def auth(self): try: client = JIRA(server=self.server_url, basic_auth=( self.username, self.password), max_retries=1) client.project(self.project) return client except: print("Authentication Error")
def __authenticate(self): client = JIRA( server=self.server, basic_auth=( self.username, self.password ) ) client.project(self.project) return client
class JiraAccess: def __init__(self, hostname, username, password): self.jira = JIRA(hostname, basic_auth=(username, password)) @staticmethod def has_field(issue, field_name): return hasattr(issue.fields, field_name) and getattr( issue.fields, field_name) is not None @staticmethod def get_story_points(issue): story_points_field_name = "customfield_10092" if JiraAccess.has_field(issue, story_points_field_name): return int(getattr(issue.fields, story_points_field_name)) else: return None @staticmethod def get_jql_field_and_week(filter_id, issue_field_name, week_index): issues_in_week_jql = f'filter = {filter_id} AND issuetype in standardIssueTypes() AND {issue_field_name} >= ' \ f'startOfWeek({week_index}w)' \ f'AND {issue_field_name} <= endOfWeek({week_index}w)' return issues_in_week_jql def get_projects(self): self.jira.projects() def get_project(self, project_name): self.jira.project(project_name) def get_issue(self, issue_key): self.jira.issue(issue_key) def get_sprints_data(self, board_id, text_in_board_name): sprints = self.jira.sprints(board_id) sprints_list = list( filter(lambda x: f"{text_in_board_name}" in x.name, sprints))[-6:] for s in sprints_list: sprint_issues = self.jira.search_issues(f"sprint = {s.id}") sprint_bugs = self.jira.search_issues( f"sprint = {s.id} AND issueType = BUG") return SprintData(s.id, s.name, -1, len(sprint_bugs), len(sprint_issues)) # TODO: @cache results def get_resolved_in_week(self, filter_id, week_index): resolved_issues_in_week_jql = JiraAccess.get_jql_field_and_week( filter_id, "resolved", week_index) resolved_issues_in_week = self.jira.search_issues( resolved_issues_in_week_jql) return resolved_issues_in_week
def main(): args = handleArgs() configuration = loadConfiguration() project_name = configuration["project-key"] user_name = '' jira = '' if configuration["auth-type"] == "http": user_name = configuration["http-user"]["name"] jira = JIRA(configuration["options"], auth=(configuration["http-user"]["name"], configuration["http-user"]["password"] )) # a username/password tuple else: print("Authentication appears to be configured incorrectly") exit() project = jira.project(project_name) if len(args.mode) < 1: print("Please supply a mode!") exit() if args.mode[0] == 'list': tc.listIssues(jira, project_name) elif args.mode[0] == 'create': issue = ti.createIssue(user_name) issue['project']['id'] = project.id tc.createIssue(jira, issue) elif args.mode[0] == 'view': if len(args.search_string) < 1: print("Please provide an issue key") exit() issue = tc.openIssue(jira, args.search_string[0]) ti.viewIssue(issue) elif args.mode[0] == 'edit': if len(args.search_string) < 1: print("Please provide an issue key") exit() issue = tc.openIssue(jira, args.search_string[0]) ti.editIssue(issue) elif args.mode[0] == 'resolve': if len(args.search_string) < 1: print("Please provide an issue key") exit() issue = tc.openIssue(jira, args.search_string[0]) issue_transition_fields = ti.resolveIssue() issue_transition_fields['assignee']['name'] = user_name tc.tranisitionIssue(jira, issue, issue_transition_fields, 'Resolve Issue') elif args.mode[0] == "qresolve": if len(args.search_string) < 1: print("Please provide an issue key") exit() if len(args.message) < 1: print('Please provide a resolution message') exit() issue = tc.openIssue(jira, args.search_string[0]) issue_transition_fields = ti.resolveIssue(args.message[0]) issue_transition_fields['assignee']['name'] = user_name tc.tranisitionIssue(jira, issue, issue_transition_fields, 'Resolve Issue') else: print("Unrecognized Command")
def jira_prj_versions(project, version_range): (from_str, to_str) = version_range.split('-', 1) from_ver = parse_version(from_str) to_ver = parse_version(to_str) if from_ver == None or to_ver == None: raise ValueError("Cannot parse range %s" % version_range) if type(from_ver) != type(to_ver): raise ValueError("Inconsistent types in range %s" % version_range) jira = JIRA(server="https://jira.opendaylight.org") prj = jira.project(project) versions = set() for version in jira.project_versions(prj): name = version.name ver = parse_version(name) if type(ver) == type(from_ver) and ver >= from_ver and ver <= to_ver: versions.add(name) if len(versions) == 0: raise ValueError("No versions selected for project %s range %s" % (project, version_range)) versions = list(versions) versions.sort() versions = ", ".join(versions) return (jira, prj, from_str, to_str, versions)
def GET(self): gl.GL_WEBINPUT = web.input() projectname = gl.GL_WEBINPUT.product_name productline_list = gl.GL_DB.query("select distinct version from crashinfo where appName='" + projectname + "'") # select *, count(distinct name) from table group by name result = [] result_g_version = [] result_jira_version = [] #jira = JIRA(server='http://127.0.0.1:1194', basic_auth=('wangyang', 'qwerty123456')) jira = JIRA(server=jira_server, basic_auth=(user_accout, user_pass)) for name in project: if name in projectname: jira_name = project[name]["projectname"] versions = jira.project_versions(jira.project(jira_name)) for item in productline_list: item_dict = {"game_version": item.version} result_g_version.append(item_dict) [result_jira_version.append({"jira_version": v.name}) for v in reversed(versions)] result.append(result_g_version) result.append(result_jira_version) encodedjson = json.dumps(result) return encodedjson
def update_zephyr(test_cases_list): args = parse_zapi_config() if "JIRA_URL" == args['jira_url']: print("Zephyr is not configured, skipping...") return """"Main routine""" jira = JIRA(basic_auth=(args["user"], args["passwd"]), options={'server': args["jira_url"]}) proj = jira.project(args["project"]) #verid = get_jira_release_id(args.release, jira, proj) verid = "32880" cycleName = args["cycle"] cycleName = cycleName + "_" + str( (datetime.datetime.now()).strftime("%Y%m%d%H%M%S")) reporter = zapi.Zapi(project_id=proj.id, version_id=verid, environment=str(args["environment"]), build=args["build"], jira_url=args["jira_url"], usr=args["user"], pwd=args["passwd"]) if args["cycle"] is None: args["cycle"] = args["build"] reporter.get_or_create_cycle(str(cycleName)) result = "" for i in range(len(test_cases_list)): test_name = test_cases_list[i][0] print "Test_name :" + test_name test_id = get_test_id_from_meta_file(args["metafile"], test_name) if test_id: print "Found Test ID in Meta file : " + test_id issue = jira.issue(test_id) if args["updateautomationstatus"]: update_automation_status(issue) exec_id = reporter.create_execution(str(issue.id)) result = test_cases_list[i][1] print "Test case Result: " + result log_data = "sample log data" if result == 'FAIL': result = 'FAIL' if result == 'OK': result = 'PASS' if result == 'None': result = 'FAIL' if result == 'SKIP': result = 'NOT TESTED' if result == 'Exp FAIL': result = 'FAIL' reporter.set_execution(result, exec_id, log_data)
def execute(args_list): args = parse_args(args_list) print("Running JIRA Tabulations for Releases") jira_options = {"server": "https://battlefy.atlassian.net"} jira = JIRA( options=jira_options, basic_auth=(args.user, args.api_token), ) releases = args.releases.split(",") releases_container = [] project_configs = {} for release in releases: release_obj = Release(release, [], 0.0) releases_container.append(release_obj) query_string = "fixVersion={}".format(release) # get a list of the issues first, just by summary and comprehend the # projects issue_projects = list( set([ e.fields.project.id for e in jira.search_issues(query_string) ])) if len(issue_projects) != 1: print("Multiple projects in release; unable to assert size.") return -1 root_project_id = issue_projects[0] if root_project_id not in project_configs: project_configs[ root_project_id] = epicTimeRollup.generate_project_constants( jira, jira.project(root_project_id)) cust_keys = list( set([ project_configs[root_project_id].story.estimation_key, project_configs[root_project_id].task.estimation_key ])) cust_key_str = ",".join(cust_keys) release_obj.issues = [ epicTimeRollup.UserStory( e, e.fields.subtasks if hasattr(e.fields, "subtasks") else [], 0.0) for e in jira.search_issues( query_string, fields="{}, subtasks, summary, issuetype".format(cust_key_str)) ] for release in releases_container: for issue in release.issues: epicTimeRollup.extract_issue_estimate( jira, issue, project_configs[root_project_id]) release.summed_time += issue.summed_time if args.export_estimates: export_releases_json(args.export_estimates_path, releases_container)
def generate_page(): options = {'server': 'http://shaipjira.local.tmvse.com'} jira = JIRA(options) project = jira.project('10002') requirements = jira.search_issues( 'project = SHAIP AND issuetype = Requirement AND resolution = Unresolved ORDER BY id ASC', expand="renderedFields") template_requirements = [] for requirement in requirements: links = [] for link in requirement.fields.issuelinks: if link.type.name != "Defines": continue link_type = None issue = None if hasattr(link, 'outwardIssue'): link_type = "defines" issue = link.outwardIssue else: link_type = "is defined by" issue = link.inwardIssue links.append({ 'key': issue.key, 'summary': issue.fields.summary, 'icon_type': issue.fields.issuetype.iconUrl, 'permalink': issue.permalink(), 'closed': issue.fields.status.name == "Done", 'link_type': link_type }) template_requirements.append({ 'key': requirement.key, 'summary': requirement.fields.summary, 'description': requirement.renderedFields.description, 'links': links, 'permalink': requirement.permalink(), 'labels': requirement.fields.labels }) renderer = pystache.Renderer() context = { 'project_name': project.name, 'requirements': template_requirements, 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } return renderer.render_path("templates/requirements.html.mustache", context)
def version_update_reporter(bot): jira_access = JIRA( server="https://bugs.mojang.com", basic_auth=(mojira_username, mojira_password), ) # All the different minecraft versions on the bug tracker versions = jira_access.project("MC").versions embed = discord.Embed(color=discord.Colour.magenta()) # We check for the different kinds of version changes. # We can just return after that since updates between the different version updates will never be faster # than the period of the loop. # Check for new versions, if there are we send the embed containing that info. new = new_version(bot, versions) if new: embed.title = str(new) embed.add_field(name="Affected", value=str(affected_bugs(str(new), jira_access))) embed.add_field(name="Fixed", value=str(fixed_bugs(str(new), jira_access))) content = "Version **{}** has just been created!".format(str(new)) jira_access.close() return embed, content # Check for archived versions, if there are we send the embed containing that info. archived = archived_version(bot, versions) if archived: embed.title = str(archived) embed.add_field(name="Affected", value=str(affected_bugs(str(archived), jira_access))) embed.add_field(name="Fixed", value=str(fixed_bugs(str(archived), jira_access))) embed.add_field(name="Released", value=str(archived.released)) content = "Version **{}** has just been archived!".format(str(archived)) jira_access.close() return embed, content # Check for released versions, if there are we send the embed containing that info. released = released_version(bot, versions) if released: embed.title = str(released) embed.add_field(name="Affected", value=str(affected_bugs(str(released), jira_access))) embed.add_field(name="Fixed", value=str(fixed_bugs(str(released), jira_access))) embed.add_field(name="Released", value=str(released.released)) content = "Version **{}** has just been released!".format(str(released)) jira_access.close() return embed, content jira_access.close() return None
class Jira: def __init__(self): self.user = "******" self.apikey = "your_jira_api_key" self.server = "https://your_emakina_jira_instance.com" self.project = "your_project_name" self.options = {'server': self.server} self.jira = JIRA(self.options, basic_auth=(self.user, self.apikey)) def create_issue(self, summary, description, timestamp): self.jira.project(self.project) self.jira.create_issue(project=self.project, summary=f"{summary} {timestamp}", description=f"{description}", issuetype={"name": "Task"}) def add_attachment_to_ticket(self, filename): self.jira.add_attachment( issue=self.jira.search_issues(f'project={self.project}')[0], attachment=f"{filename}")
def get_project(params): api_server = params['api_url'] api_username = params['api_username'] api_password = params['api_password'] auth = (api_username, api_password) jira = JIRA( {'server': api_server}, basic_auth=auth) key = params['project_key'] try: return jira.project(key) except: return None
class JiraConnection(object): """Manages a connection to a Jira database""" def __init__(self): self.jira = None self.project = None def connect(self, url, user, passwd): """ Connect to a Jira server Arguments: url -- URL of the Jira server user -- username passwd -- password """ options = {'server': url} self.jira = JIRA(options, basic_auth=(user, passwd)) def get_project_list(self): """ Returns a list of available projects. Projects are tuples with the format (key, name). """ plist = [] for project in self.jira.projects(): plist.append((project.key, project.name)) return plist def set_project(self, key): """ Sets the active project. Arguments: key -- project key """ self.project = self.jira.project(key) def get_epic_list(self, project=None): """ Returns a list of epics for the project. Arguments: project -- project key (optional if project is set) """ if project is None and self.project is not None: project = self.project.key else: raise ValueError('A project is required') return self.jira.search_issues('project={0} and type=Epic'.format(project))
def validate_config_value(config): """ :param config: Project XML config class object :return: bool True if Project XML config values are valid else raises Exception """ try: logger.debug("Connecting with Project JIRA server at URL [{}]".format(config.url)) jira = JIRA(server=config.url, basic_auth=(config.user, config.passwd)) logger.debug("Successfully connected!") logger.debug("Checking if project key exists [{0}] in server".format(config.key)) name = jira.project(config.key).name logger.debug("Project key [{0}] with name [{1}] exists in server".format(config.key, name)) return True except Exception: raise
def get_versions(bot): jira_access = JIRA( server="https://bugs.mojang.com", basic_auth=(mojira_username, mojira_password), ) versions = jira_access.project("MC").versions bot.mc_versions = {str(version) for version in versions} bot.latest_version = str(versions[-1]) bot.previous_version = str(versions[-2]) bot.latest_version_archive_status = versions[-1].archived bot.previous_version_archive_status = versions[-2].archived bot.latest_version_release_status = versions[-1].released jira_access.close()
class JIRACLASS(object): def __init__(self): self.jira = JIRA({'server': 'https://rajsimhag.atlassian.net'}, basic_auth=('*****@*****.**', 'gautham999')) def getProjects(self, data): return self.jira.project(data) def getIssue(self, id): return self.jira.issue(id) def createIssue(self, data): return self.jira.create_issue(data) def deleteIssue(self, id): self.jira.delete_issue(id)
class JIRACLASS(object): def __init__(self): self.jira = JIRA( {'server': 'https://manojteluguntla.atlassian.net'}, basic_auth=('*****@*****.**', 'Western852$@')) def getProjects(self, data): return self.jira.project(data) def getIssue(self, id): return self.jira.issue(id) def createIssue(self, data): return self.jira.create_issue(data) def deleteIssue(self, id): self.jira.delete_issue(id)
def validate_config_value(config): """ :param config: Project XML config class object :return: bool True if Project XML config values are valid else raises Exception """ try: logger.debug("Connecting with Project JIRA server at URL [{}]".format( config.url)) jira = JIRA(server=config.url, basic_auth=(config.user, config.passwd)) logger.debug("Successfully connected!") logger.debug("Checking if project key exists [{0}] in server".format( config.key)) name = jira.project(config.key).name logger.debug( "Project key [{0}] with name [{1}] exists in server".format( config.key, name)) return True except Exception: raise
class JiraService: def __init__(self): self.jira = JIRA(AppConstants.JIRA_SERVER_URL, basic_auth=(AppConstants.JIRA_USERNAME, AppConstants.JIRA_PASSWORD)) def get_all_projects(self) -> List[Project]: projects = self.jira.projects() return projects def get_project(self, project_id: str) -> Project: project = self.jira.project(id=project_id) return project def get_issues_for_project(self, project_id: str, max_results: int) -> List[Issue]: issues = self.jira.search_issues(f"project = '{project_id}'", maxResults=max_results) return issues
class JIRAProject: """ Custom class which represents a JIRA project for DSR report """ JQL = 'project="{project}" AND ((created >= "{start}" and created <= "{end}") ' \ 'OR (updated >= "{start}" and updated <= "{end}")) order by updated asc' def __init__(self, config, start_date, end_date): self.config = config self.start_date = start_date self.end_date = end_date self.jira = JIRA(server=config.url, basic_auth=(config.user, config.passwd)) self.timezone = pytz.timezone(config.timezone) self.name = self.jira.project(config.key).name def __str__(self): return self.name def __eq__(self, other): return self.config.key == other def get_project_issues(self): """ This method returns a list of JIRA issue objects which have any activities within specified start and end date s :return: [JIRAIssue objects] """ jira_issues_in_proj = self.jira.search_issues( JIRAProject.JQL.format(project=self.config.key, start=self.start_date, end=self.end_date)) if jira_issues_in_proj: project_issues = list() for issue in jira_issues_in_proj: project_issue = JIRAIssue(self, self.jira, issue.key, self.start_date, self.end_date) project_issues.append(project_issue) return project_issues
def create_project(params): api_server = params['api_url'] api_username = params['api_username'] api_password = params['api_password'] auth = (api_username, api_password) jira = JIRA( {'server': api_server}, basic_auth=auth) project_key = params['project_key'] project_name = params['project_name'] assignee = params['assignee'] # create project and view if not jira.create_project(project_key, name=project_name, assignee=assignee): return None proj = jira.project(project_key) board = jira.create_board("{0} View".format(project_name), project_key, preset='scrum') issue = jira.create_issue( project=project_key, summary="Sample Issue for {0}".format(project_name), description="This is just sample", issuetype={"name":"Task"} ) return proj
def push_ticket(jira: Jira, ticket: JiraTicket): """Push a JiraTicket to a Jira server. Args: jira: The Jira server to get tickets from. ticket: The ticket to push to the Jira server. """ # Create the ticket ticket_fields = { "description": add_source_link_to_description( ticket.description, ticket.source_link ), "issuetype": {"name": "Task"}, "priority": {"name": ticket.priority}, "project": {"id": jira.project(ticket.project).id}, "summary": ticket.summary, } new_ticket = jira.create_issue(fields=ticket_fields) # Transition the ticket if ticket.resolution is not None: # List available transitions and search for the one we want (if # it exists) transitions = jira.transitions(new_ticket) id_ = None for transition in transitions: if transition["name"] == ticket.resolution: # Found it id_ = transition["id"] break # Transition not available. That's okay. if id_ is None: return jira.transition_issue(new_ticket, id_)
class Api(object): versions = None def __init__(self): self.j = JIRA( server=settings.JIRA_URL, basic_auth=(settings.JIRA_USERNAME, settings.JIRA_PASSWORD), ) self.project = self.j.project(settings.JIRA_PROJECT) def get_versions(self): return self.j.project_versions(self.project) def get_issues(self, version_name, username): if self.versions is None: self.versions = self.get_versions() version = next( (v for v in self.versions if version_name in v.name and '1C' not in v.name and '1С' not in v.name and 'WWW' not in v.name ), None ) if version is None: return [] else: jql = 'project=VIP ' \ 'AND resolution=Unresolved ' \ 'AND fixVersion="{fixVersion}"' \ 'AND assignee in ({assignee})'.format( fixVersion=version.name, assignee=username, ) return self.j.search_issues(jql)
def update_zephyr(test_cases_list): args = parse_zapi_config() if len(args) == 0: print("Zephyr is not configured, skipping...") return print('Starting Zephyr Execution....') for z in args: if "JIRA_URL" == z['jira_url'] or "JIRAPASSWORD" == z['passwd']: # This is not configure, skip to next continue """"Main routine""" jira = JIRA(basic_auth=(z["user"], z["passwd"]), options={'server': z["jira_url"]}) proj = jira.project(z["project"]) verid = get_jira_release_id(z['release'], jira, proj) cycleName = z["cycle"] cycleName = cycleName + "_" + str( (datetime.datetime.now()).strftime("%Y%m%d%H%M%S")) reporter = zapi.Zapi(project_id=proj.id, version_id=verid, environment=str(z["environment"]), build=z["build"], jira_url=z["jira_url"], usr=z["user"], pwd=z["passwd"]) if z["cycle"] is None: z["cycle"] = z["build"] reporter.get_or_create_cycle(str(cycleName)) result = "" for i in range(len(test_cases_list)): test_name = test_cases_list[i][0] print("Test_name :" + test_name) test_id = get_test_id_from_meta_file(z["metafile"], test_name) if test_id: print("Found Test ID in Meta file : " + test_id) issue = jira.issue(test_id) else: continue if z["updateautomationstatus"]: update_automation_status(issue) exec_id = reporter.create_execution(str(issue.id)) result = test_cases_list[i][1] print("Test case Result: " + result) log_data = "sample log data" if result == 'FAIL': result = 'FAIL' if result == 'OK': result = 'PASS' if result == 'None': result = 'FAIL' if result == 'SKIP': result = 'NOT TESTED' if result == 'Exp FAIL': result = 'FAIL' if 'status_codes' in z: ret = reporter.set_execution( result, exec_id, log_data, status_code_dict=z['status_codes']) else: ret = reporter.set_execution(result, exec_id, log_data) if ret.status_code != requests.codes.ok: raise Exception( "Error = %s, when trying to set execution status" % ret)
class JiraAPI(object): def __init__(self, hostname=None, username=None, password=None, path="", debug=False, clean_obsolete=True, max_time_window=12): self.logger = logging.getLogger('JiraAPI') if debug: self.logger.setLevel(logging.DEBUG) if "https://" not in hostname: hostname = "https://{}".format(hostname) self.username = username self.password = password self.jira = JIRA(options={'server': hostname}, basic_auth=(self.username, self.password)) self.logger.info("Created vjira service for {}".format(hostname)) self.all_tickets = [] self.JIRA_REOPEN_ISSUE = "Reopen Issue" self.JIRA_CLOSE_ISSUE = "Close Issue" self.max_time_tracking = max_time_window #in months #<JIRA Resolution: name=u'Obsolete', id=u'11'> self.JIRA_RESOLUTION_OBSOLETE = "Obsolete" self.JIRA_RESOLUTION_FIXED = "Fixed" self.clean_obsolete = clean_obsolete self.template_path = 'vulnwhisp/reporting/resources/ticket.tpl' if path: self.download_tickets(path) else: self.logger.warn( "No local path specified, skipping Jira ticket download.") def create_ticket(self, title, desc, project="IS", components=[], tags=[]): labels = ['vulnerability_management'] for tag in tags: labels.append(str(tag)) self.logger.info("creating ticket for project {} title[20] {}".format( project, title[:20])) self.logger.info("project {} has a component requirement: {}".format( project, self.PROJECT_COMPONENT_TABLE[project])) project_obj = self.jira.project(project) components_ticket = [] for component in components: exists = False for c in project_obj.components: if component == c.name: self.logger.debug( "resolved component name {} to id {}".format( c.name, c.id)) components_ticket.append({"id": c.id}) exists = True if not exists: self.logger.error( "Error creating Ticket: component {} not found".format( component)) return 0 new_issue = self.jira.create_issue(project=project, summary=title, description=desc, issuetype={'name': 'Bug'}, labels=labels, components=components_ticket) self.logger.info("Ticket {} created successfully".format(new_issue)) return new_issue #Basic JIRA Metrics def metrics_open_tickets(self, project=None): jql = "labels= vulnerability_management and resolution = Unresolved" if project: jql += " and (project='{}')".format(project) self.logger.debug('Executing: {}'.format(jql)) return len(self.jira.search_issues(jql, maxResults=0)) def metrics_closed_tickets(self, project=None): jql = "labels= vulnerability_management and NOT resolution = Unresolved" if project: jql += " and (project='{}')".format(project) return len(self.jira.search_issues(jql, maxResults=0)) def sync(self, vulnerabilities, project, components=[]): #JIRA structure of each vulnerability: [source, scan_name, title, diagnosis, consequence, solution, ips, risk, references] self.logger.info("JIRA Sync started") # [HIGIENE] close tickets older than 12 months as obsolete # Higiene clean up affects to all tickets created by the module, filters by label 'vulnerability_management' if self.clean_obsolete: self.close_obsolete_tickets() for vuln in vulnerabilities: # JIRA doesn't allow labels with spaces, so making sure that the scan_name doesn't have spaces # if it has, they will be replaced by "_" if " " in vuln['scan_name']: vuln['scan_name'] = "_".join(vuln['scan_name'].split(" ")) exists = False to_update = False ticketid = "" ticket_assets = [] exists, to_update, ticketid, ticket_assets = self.check_vuln_already_exists( vuln) if exists: # If ticket "resolved" -> reopen, as vulnerability is still existent self.reopen_ticket(ticketid) self.add_label(ticketid, vuln['risk']) continue elif to_update: self.ticket_update_assets(vuln, ticketid, ticket_assets) self.add_label(ticketid, vuln['risk']) continue try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception templating: {}'.format(str(e))) return 0 self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[ vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk'] ]) self.close_fixed_tickets(vulnerabilities) # we reinitialize so the next sync redoes the query with their specific variables self.all_tickets = [] return True def check_vuln_already_exists(self, vuln): # we need to return if the vulnerability has already been reported and the ID of the ticket for further processing #function returns array [duplicated(bool), update(bool), ticketid, ticket_assets] title = vuln['title'] labels = [ vuln['source'], vuln['scan_name'], 'vulnerability_management', 'vulnerability' ] #list(set()) to remove duplicates assets = list( set( re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) if not self.all_tickets: self.logger.info( "Retrieving all JIRA tickets with the following tags {}". format(labels)) # we want to check all JIRA tickets, to include tickets moved to other queues # will exclude tickets older than 12 months, old tickets will get closed for higiene and recreated if still vulnerable jql = "{} AND NOT labels=advisory AND created >=startOfMonth(-{})".format( " AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking) self.all_tickets = self.jira.search_issues(jql, maxResults=0) #WARNING: function IGNORES DUPLICATES, after finding a "duplicate" will just return it exists #it wont iterate over the rest of tickets looking for other possible duplicates/similar issues self.logger.info("Comparing Vulnerabilities to created tickets") for index in range(len(self.all_tickets) - 1): checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields( self.all_tickets[index]) if title == checking_title: difference = list( set(assets).symmetric_difference(checking_assets)) #to check intersection - set(assets) & set(checking_assets) if difference: self.logger.info( "Asset mismatch, ticket to update. Ticket ID: {}". format(checking_ticketid)) return False, True, checking_ticketid, checking_assets #this will automatically validate else: self.logger.info( "Confirmed duplicated. TickedID: {}".format( checking_ticketid)) return True, False, checking_ticketid, [ ] #this will automatically validate return False, False, "", [] def ticket_get_unique_fields(self, ticket): title = ticket.raw.get('fields', {}).get('summary').encode("ascii").strip() ticketid = ticket.key.encode("ascii") try: affected_assets_section = ticket.raw.get( 'fields', {}).get('description').encode("ascii").split( "{panel:title=Affected Assets}")[1].split("{panel}")[0] assets = list( set( re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section))) except: self.logger.error( "Ticket IPs regex failed. Ticket ID: {}".format(ticketid)) assets = [] return ticketid, title, assets def get_ticket_reported_assets(self, ticket): #[METRICS] return a list with all the affected assets for that vulnerability (including already resolved ones) return list( set( re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", str(self.jira.issue(ticket).raw)))) def get_resolution_time(self, ticket): #get time a ticket took to be resolved ticket_obj = self.jira.issue(ticket) if self.is_ticket_resolved(ticket_obj): ticket_data = ticket_obj.raw.get('fields') #dates follow format '2018-11-06T10:36:13.849+0100' created = [ int(x) for x in ticket_data['created'].split('.')[0].replace( 'T', '-').replace(':', '-').split('-') ] resolved = [ int(x) for x in ticket_data['resolutiondate'].split('.')[0].replace( 'T', '-').replace(':', '-').split('-') ] start = datetime(created[0], created[1], created[2], created[3], created[4], created[5]) end = datetime(resolved[0], resolved[1], resolved[2], resolved[3], resolved[4], resolved[5]) return (end - start).days else: self.logger.error( "Ticket {ticket} is not resolved, can't calculate resolution time" .format(ticket=ticket)) return False def ticket_update_assets(self, vuln, ticketid, ticket_assets): # correct description will always be in the vulnerability to report, only needed to update description to new one self.logger.info("Ticket {} exists, UPDATE requested".format(ticketid)) if self.is_ticket_resolved(self.jira.issue(ticketid)): self.reopen_ticket(ticketid) try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception updating assets: {}'.format(str(e))) return 0 ticket_obj = self.jira.issue(ticketid) ticket_obj.update() assets = list( set( re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) difference = list(set(assets).symmetric_difference(ticket_assets)) comment = '' #put a comment with the assets that have been added/removed for asset in difference: if asset in assets: comment += "Asset {} have been added to the ticket as vulnerability *has been newly detected*.\n".format( asset) elif asset in ticket_assets: comment += "Asset {} have been removed from the ticket as vulnerability *has been resolved*.\n".format( asset) try: ticket_obj.update(description=tpl, comment=comment, fields={"labels": ticket_obj.fields.labels}) self.logger.info("Ticket {} updated successfully".format(ticketid)) self.add_label(ticketid, 'updated') except: self.logger.error( "Error while trying up update ticket {}".format(ticketid)) return 0 def add_label(self, ticketid, label): ticket_obj = self.jira.issue(ticketid) if label not in ticket_obj.fields.labels: ticket_obj.fields.labels.append(label) try: ticket_obj.update(fields={"labels": ticket_obj.fields.labels}) self.logger.info("Added label {label} to ticket {ticket}".format( label=label, ticket=ticketid)) except: self.logger.error( "Error while trying to add label {label} to ticket {ticket}". format(label=label, ticket=ticketid)) return 0 def close_fixed_tickets(self, vulnerabilities): # close tickets which vulnerabilities have been resolved and are still open found_vulns = [] for vuln in vulnerabilities: found_vulns.append(vuln['title']) comment = '''This ticket is being closed as it appears that the vulnerability no longer exists. If the vulnerability reappears, a new ticket will be opened.''' for ticket in self.all_tickets: if ticket.raw['fields']['summary'].strip() in found_vulns: self.logger.info( "Ticket {} is still vulnerable".format(ticket)) continue self.logger.info( "Ticket {} is no longer vulnerable".format(ticket)) self.close_ticket(ticket, self.JIRA_RESOLUTION_FIXED, comment) return 0 def is_ticket_reopenable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_REOPEN_ISSUE: self.logger.debug("Ticket is reopenable") return True self.logger.warn("Ticket can't be opened. Check Jira transitions.") return False def is_ticket_closeable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_CLOSE_ISSUE: return True self.logger.warn("Ticket can't closed. Check Jira transitions.") return False def is_ticket_resolved(self, ticket_obj): #Checks if a ticket is resolved or not if ticket_obj is not None: if ticket_obj.raw['fields'].get('resolution') is not None: if ticket_obj.raw['fields'].get('resolution').get( 'name') != 'Unresolved': self.logger.debug( "Checked ticket {} is already closed".format( ticket_obj)) self.logger.info("Ticket {} is closed".format(ticket_obj)) return True self.logger.debug( "Checked ticket {} is already open".format(ticket_obj)) return False def is_risk_accepted(self, ticket_obj): if ticket_obj is not None: if ticket_obj.raw['fields'].get('labels') is not None: labels = ticket_obj.raw['fields'].get('labels') if "risk_accepted" in labels: self.logger.warn( "Ticket {} accepted risk, will be ignored".format( ticket_obj)) return True elif "server_decomission" in labels: self.logger.warn( "Ticket {} server decomissioned, will be ignored". format(ticket_obj)) return True self.logger.info( "Ticket {} risk has not been accepted".format(ticket_obj)) return False def reopen_ticket(self, ticketid): self.logger.debug( "Ticket {} exists, REOPEN requested".format(ticketid)) # this will reopen a ticket by ticketid ticket_obj = self.jira.issue(ticketid) if self.is_ticket_resolved(ticket_obj): if not self.is_risk_accepted(ticket_obj): try: if self.is_ticket_reopenable(ticket_obj): comment = '''This ticket has been reopened due to the vulnerability not having been fixed (if multiple assets are affected, all need to be fixed; if the server is down, lastest known vulnerability might be the one reported). In the case of the team accepting the risk and wanting to close the ticket, please add the label "*risk_accepted*" to the ticket before closing it. If server has been decomissioned, please add the label "*server_decomission*" to the ticket before closing it. If you have further doubts, please contact the Security Team.''' error = self.jira.transition_issue( issue=ticketid, transition=self.JIRA_REOPEN_ISSUE, comment=comment) self.logger.info( "Ticket {} reopened successfully".format(ticketid)) self.add_label(ticketid, 'reopened') return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error reopening ticket {}: {}".format( ticketid, e)) return 0 return 0 def close_ticket(self, ticketid, resolution, comment): # this will close a ticket by ticketid self.logger.debug("Ticket {} exists, CLOSE requested".format(ticketid)) ticket_obj = self.jira.issue(ticketid) if not self.is_ticket_resolved(ticket_obj): try: if self.is_ticket_closeable(ticket_obj): #need to add the label before closing the ticket self.add_label(ticketid, 'closed') error = self.jira.transition_issue( issue=ticketid, transition=self.JIRA_CLOSE_ISSUE, comment=comment, resolution={"name": resolution}) self.logger.info( "Ticket {} closed successfully".format(ticketid)) return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error closing ticket {}: {}".format( ticketid, e)) return 0 return 0 def close_obsolete_tickets(self): # Close tickets older than 12 months, vulnerabilities not solved will get created a new ticket self.logger.info( "Closing obsolete tickets older than {} months".format( self.max_time_tracking)) jql = "labels=vulnerability_management AND created <startOfMonth(-{}) and resolution=Unresolved".format( self.max_time_tracking) tickets_to_close = self.jira.search_issues(jql, maxResults=0) comment = '''This ticket is being closed for hygiene, as it is more than 12 months old. If the vulnerability still exists, a new ticket will be opened.''' for ticket in tickets_to_close: self.close_ticket(ticket, self.JIRA_RESOLUTION_OBSOLETE, comment) return 0 def project_exists(self, project): try: self.jira.project(project) return True except: return False return False def download_tickets(self, path): #saves all tickets locally, local snapshot of vulnerability_management ticktes #check if file already exists check_date = str(date.today()) fname = '{}jira_{}.json'.format(path, check_date) if os.path.isfile(fname): self.logger.info( "File {} already exists, skipping ticket download".format( fname)) return True try: self.logger.info( "Saving locally tickets from the last {} months".format( self.max_time_tracking)) jql = "labels=vulnerability_management AND created >=startOfMonth(-{})".format( self.max_time_tracking) tickets_data = self.jira.search_issues(jql, maxResults=0) #end of line needed, as writelines() doesn't add it automatically, otherwise one big line to_save = [ json.dumps(ticket.raw.get('fields')) + "\n" for ticket in tickets_data ] with open(fname, 'w') as outfile: outfile.writelines(to_save) self.logger.info("Tickets saved succesfully.") return True except Exception as e: self.logger.error( "Tickets could not be saved locally: {}.".format(e)) return False
parser.error("Must to specify ALL commandline flags") jiraserver = options.jiraserver jbide_fixversion = options.jbidefixversion jbds_fixversion = options.jbdsfixversion from components import checkFixVersionsExist, queryComponentLead, defaultAssignee if checkFixVersionsExist(jbide_fixversion, jbds_fixversion, jiraserver, options.usernameJIRA, options.passwordJIRA) == True: frombranch = options.frombranch jira = JIRA(options={'server': jiraserver}, basic_auth=(options.usernameJIRA, options.passwordJIRA)) CLJBIDE = jira.project_components( jira.project('JBIDE')) # full list of components in JBIDE CLJBDS = jira.project_components( jira.project('JBDS')) # full list of components in JBIDE taskdescription = options.taskdescription taskdescriptionfull = options.taskdescriptionfull if not options.taskdescriptionfull: taskdescriptionfull = options.taskdescription ## The jql query across for all task issues tasksearchquery = '((project in (JBDS) and fixVersion = "' + jbds_fixversion + '") or (project in (JBIDE) and fixVersion = "' + jbide_fixversion + '")) AND labels = task' tasksearch = jiraserver + '/issues/?jql=' + urllib.quote_plus( tasksearchquery) rootJBDS_dict = {
class JiraTestManager(object): """Used to instantiate and populate the JIRA instance with data used by the unit tests. Attributes: CI_JIRA_ADMIN (str): Admin user account name. CI_JIRA_USER (str): Limited user account name. max_retries (int): number of retries to perform for recoverable HTTP errors. """ # __metaclass__ = Singleton # __instance = None # # Singleton implementation # def __new__(cls, *args, **kwargs): # if not cls.__instance: # cls.__instance = super(JiraTestManager, cls).__new__( # cls, *args, **kwargs) # return cls.__instance # Implementing some kind of Singleton, to prevent test initialization # http://stackoverflow.com/questions/31875/is-there-a-simple-elegant-way-to-define-singletons-in-python/33201#33201 __shared_state = {} @retry(stop=stop_after_attempt(2)) def __init__(self): self.__dict__ = self.__shared_state if not self.__dict__: self.initialized = 0 try: if 'CI_JIRA_URL' in os.environ: self.CI_JIRA_URL = os.environ['CI_JIRA_URL'] self.max_retries = 5 else: self.CI_JIRA_URL = "https://pycontribs.atlassian.net" self.max_retries = 5 if 'CI_JIRA_ADMIN' in os.environ: self.CI_JIRA_ADMIN = os.environ['CI_JIRA_ADMIN'] else: self.CI_JIRA_ADMIN = 'ci-admin' if 'CI_JIRA_ADMIN_PASSWORD' in os.environ: self.CI_JIRA_ADMIN_PASSWORD = os.environ[ 'CI_JIRA_ADMIN_PASSWORD'] else: self.CI_JIRA_ADMIN_PASSWORD = '******' if 'CI_JIRA_USER' in os.environ: self.CI_JIRA_USER = os.environ['CI_JIRA_USER'] else: self.CI_JIRA_USER = '******' if 'CI_JIRA_USER_PASSWORD' in os.environ: self.CI_JIRA_USER_PASSWORD = os.environ[ 'CI_JIRA_USER_PASSWORD'] else: self.CI_JIRA_USER_PASSWORD = '******' self.CI_JIRA_ISSUE = os.environ.get('CI_JIRA_ISSUE', 'Bug') if OAUTH: self.jira_admin = JIRA( oauth={ 'access_token': 'hTxcwsbUQiFuFALf7KZHDaeAJIo3tLUK', 'access_token_secret': 'aNCLQFP3ORNU6WY7HQISbqbhf0UudDAf', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA }) else: if self.CI_JIRA_ADMIN: self.jira_admin = JIRA( self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_admin = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) if self.jira_admin.current_user() != self.CI_JIRA_ADMIN: # self.jira_admin. self.initialized = 1 sys.exit(3) if OAUTH: self.jira_sysadmin = JIRA(oauth={ 'access_token': '4ul1ETSFo7ybbIxAxzyRal39cTrwEGFv', 'access_token_secret': 'K83jBZnjnuVRcfjBflrKyThJa0KSjSs2', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA }, logging=False, max_retries=self.max_retries) else: if self.CI_JIRA_ADMIN: self.jira_sysadmin = JIRA( self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_sysadmin = JIRA(self.CI_JIRA_URL, logging=False, max_retries=self.max_retries) if OAUTH: self.jira_normal = JIRA( oauth={ 'access_token': 'ZVDgYDyIQqJY8IFlQ446jZaURIz5ECiB', 'access_token_secret': '5WbLBybPDg1lqqyFjyXSCsCtAWTwz1eD', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA }) else: if self.CI_JIRA_ADMIN: self.jira_normal = JIRA( self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_USER, self.CI_JIRA_USER_PASSWORD), validate=True, logging=False, max_retries=self.max_retries) else: self.jira_normal = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) # now we need some data to start with for the tests # jira project key is max 10 chars, no letter. # [0] always "Z" # [1-6] username running the tests (hope we will not collide) # [7-8] python version A=0, B=1,.. # [9] A,B -- we may need more than one project """ `jid` is important for avoiding concurency problems when executing tests in parallel as we have only one test instance. jid length must be less than 9 characters because we may append another one and the JIRA Project key length limit is 10. Tests run in parallel: * git branches master or developer, git pr or developers running tests outside Travis * Travis is using "Travis" username https://docs.travis-ci.com/user/environment-variables/ """ self.jid = get_unique_project_name() self.project_a = self.jid + 'A' # old XSS self.project_a_name = "Test user=%s key=%s A" \ % (getpass.getuser(), self.project_a) self.project_b = self.jid + 'B' # old BULK self.project_b_name = "Test user=%s key=%s B" \ % (getpass.getuser(), self.project_b) self.project_c = self.jid + 'C' # For Service Desk self.project_c_name = "Test user=%s key=%s C" \ % (getpass.getuser(), self.project_c) # TODO(ssbarnea): find a way to prevent SecurityTokenMissing for On Demand # https://jira.atlassian.com/browse/JRA-39153 try: self.jira_admin.project(self.project_a) except Exception as e: logging.warning(e) pass else: try: self.jira_admin.delete_project(self.project_a) except Exception as e: pass try: self.jira_admin.project(self.project_b) except Exception as e: logging.warning(e) pass else: try: self.jira_admin.delete_project(self.project_b) except Exception as e: pass try: self.jira_admin.project(self.project_c) except Exception as e: logging.warning(e) pass else: try: self.jira_admin.delete_project(self.project_c) except Exception as e: pass # wait for the project to be deleted for i in range(1, 20): try: self.jira_admin.project(self.project_b) except Exception as e: break sleep(2) try: self.jira_admin.create_project(self.project_a, self.project_a_name) except Exception: # we care only for the project to exist pass self.project_a_id = self.jira_admin.project(self.project_a).id # except Exception as e: # logging.warning("Got %s" % e) # try: # assert self.jira_admin.create_project(self.project_b, # self.project_b_name) is True, "Failed to create %s" % # self.project_b try: self.jira_admin.create_project(self.project_b, self.project_b_name) except Exception: # we care only for the project to exist pass # Create project for Jira Service Desk try: self.jira_admin.create_project( self.project_c, self.project_c_name, template_name='IT Service Desk') except Exception: pass sleep(1) # keep it here as often JIRA will report the # project as missing even after is created self.project_b_issue1_obj = self.jira_admin.create_issue( project=self.project_b, summary='issue 1 from %s' % self.project_b, issuetype=self.CI_JIRA_ISSUE) self.project_b_issue1 = self.project_b_issue1_obj.key self.project_b_issue2_obj = self.jira_admin.create_issue( project=self.project_b, summary='issue 2 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue2 = self.project_b_issue2_obj.key self.project_b_issue3_obj = self.jira_admin.create_issue( project=self.project_b, summary='issue 3 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue3 = self.project_b_issue3_obj.key except Exception as e: logging.exception("Basic test setup failed") self.initialized = 1 py.test.exit("FATAL: %s\n%s" % (e, traceback.format_exc())) if not hasattr(self, 'jira_normal') or not hasattr( self, 'jira_admin'): py.test.exit("FATAL: WTF!?") self.initialized = 1 else: # already exist but we need to be sure it was initialized counter = 0 while not self.initialized: sleep(1) counter += 1 if counter > 60: logging.fatal( "Something is clearly not right with " + "initialization, killing the tests to prevent a " + "deadlock.") sys.exit(3)
logging.debug("No more folders.") packUpdateandSend(headers,GLOBAL_url_xray,mutation) mutation = "" return else: for folder in repository['folders']: processFolders(folder,project,mutation,headers) keyStats = [] timeStats = [] count= 0 for project in GLOBAL_projectRepositoryList: startExec = time.time() cloudproject = jiraCloud.project(project) r = requests.get('' + GLOBAL_onPremiseURL+'/rest/raven/1.0/api/testrepository/'+ project +'/folders', auth=HTTPBasicAuth(GLOBAL_basic_auth_user, GLOBAL_basic_auth_pass)) if r.text == "" or r.text == None or r.text=="[]": break else : try: json_data = json.loads(r.text) except: logging.error("An error as occurred - Skipping " + project +" Project") logging.error(r.text) break repository = json.loads(r.text) processFolders(repository,cloudproject,mutation,headers) if commandExecutionExceptions.export != None: fileExport.write("%s\n" % project) fileExport.flush()
from collections import defaultdict from bs4 import BeautifulSoup import requests import os.path import re from jira import JIRA import argparse import datetime server = "https://issues.couchbase.com" options = dict(server=server) jira = JIRA(options) project_code = "PYCBC" project = jira.project(project_code) print("got project {}".format(project.versions)) parser = argparse.ArgumentParser(description="Generate release notes in Asciidoc format") parser.add_argument('version',type=str) args=parser.parse_args() ver_num = args.version project_version = next(iter(filter(lambda x: x.name == ver_num, project.versions)), None) relnotes_raw = requests.get( "{}/secure/ReleaseNote.jspa?projectId={}&version={}".format(server, project.id, project_version.id)) soup = BeautifulSoup(relnotes_raw.text, 'html.parser') content = soup.find("section", class_="aui-page-panel-content") outputdir = os.path.join("build") date = datetime.date.today().strftime("{day} %B %Y").format(day=datetime.date.today().day) try: os.makedirs(outputdir) except:
def update_or_create_jira_issue(study_id, user_token, is_curator): try: params = app.config.get('JIRA_PARAMS') user_name = params['username'] password = params['password'] updated_studies = [] try: jira = JIRA(options=options, basic_auth=(user_name, password)) except: return False, 'Could not connect to JIRA server, incorrect username or password?', updated_studies # Get the MetaboLights project mtbls_project = jira.project(project) studies = [study_id] if not study_id and is_curator: studies = get_all_studies(user_token) for study in studies: study_id = study[0] user_name = study[1] release_date = study[2] update_date = study[3] study_status = study[4] curator = study[5] issue = [] summary = None # Get an issue based on a study accession search pattern search_param = "project='" + mtbls_project.key + "' AND summary ~ '" + study_id + " \\\-\\\ 20*'" issues = jira.search_issues(search_param) # project = MetaboLights AND summary ~ 'MTBLS121 ' new_summary = study_id + ' - ' + release_date.replace('-', '') + ' - ' + \ study_status + ' (' + user_name + ')' try: if issues: issue = issues[0] else: if study_status == 'Submitted': logger.info("Could not find Jira issue for " + search_param) print("Creating new Jira issue for " + search_param) issue = jira.create_issue(project=mtbls_project.key, summary='MTBLS study - To be updated', description='Created by API', issuetype={'name': 'Story'}) else: continue # Only create new cases if the study is in status Submitted except Exception: # We could not find or create a Jira issue continue summary = issue.fields.summary # Follow pattern 'MTBLS123 - YYYYMMDD - Status' try: assignee = issue.fields.assignee.name except: assignee = "" valid_curator = False jira_curator = "" if curator: if curator.lower() == 'mark': jira_curator = 'mwilliam' valid_curator = True elif curator.lower() == 'keeva': jira_curator = 'keeva' valid_curator = True else: jira_curator = "" # Release date or status has changed, or the assignee (curator) has changed if summary.startswith('MTBLS') and (summary != new_summary or assignee != jira_curator): # Add "Curation" Epic issues_to_add = [issue.key] jira.add_issues_to_epic(curation_epic, issues_to_add) # Add the Curation Epic labels = maintain_jira_labels(issue, study_status, user_name) # Add a comment to the issue. comment_text = 'Status ' + study_status + '. Database update date ' + update_date jira.add_comment(issue, comment_text) # Change the issue's summary, comments and description. issue.update(summary=new_summary, fields={"labels": labels}, notify=False) if valid_curator: # ToDo, what if the curation log is not up to date? issue.update(assignee={'name': jira_curator}) updated_studies.append(study_id) logger.info('Updated Jira case for study ' + study_id) print('Updated Jira case for study ' + study_id) except Exception: return False, 'Update failed', updated_studies return True, 'Ticket(s) updated successfully', updated_studies
from jira import JIRA jira_connect = JIRA(server='', basic_auth=('', '')) project = jira_connect.project('') jira_connect.create_issue(project='', summary=b, description=a, issuetype={'name': 'Task'})
failureDetails = failureDetails + '{code:title=' + testurl + '' + className_re + '/' + name_re + '}\n' failureDetails = failureDetails + prettyXML(s) failureDetails = failureDetails + '\n{code}\n\n' #print failureDetails rootJBIDE_dict = { 'project' : { 'key': 'JBIDE' }, 'summary' : str(len(testcaselist)) + ' Test Failure(s) in JBIDE ' + jbide_affectedversion + ' for ' + component + ' component', 'description' : failureSummary + failureDetails, 'issuetype' : { 'name' : 'Task' }, 'priority' : { 'name' :'Critical'}, 'versions' : [{ "name" : jbide_affectedversion }], 'components' : [{ "name" : component }], 'labels' : [ "testfailure" ] } jira = JIRA(options={'server':jiraserver}, basic_auth=(options.usernameJIRA, options.passwordJIRA)) CLJBIDE = jira.project_components(jira.project('JBIDE')) # full list of components in JBIDE rootJBIDE = jira.create_issue(fields=rootJBIDE_dict) componentLead = queryComponentLead(CLJBIDE, component, 0) try: jira.assign_issue(rootJBIDE, componentLead) except: print "[WARNING] Unexpected error! User {0} tried to assign {1} to {2}: {3}".format(options.usernameJIRA, rootJBIDE, componentLead, sys.exc_info()[0]) accept = raw_input("\nAccept new JIRA " + jiraserver + '/browse/' + rootJBIDE.key + " => " + componentLead + " ? [Y/n] ") if accept.capitalize() in ["N"] : rootJBIDE.delete() # see JIRA_components listing in components.py # Sample usage: see createTestFailureJIRA.py.examples.txt
class AntonJira(): def __init__(self, server, email, apitoken): self.jira_instance = JIRA(server=server, basic_auth=(email, apitoken)) def get_project(self, project_key): """ name -- project name lead.displayName -- leader name """ return self.jira_instance.project(project_key) def get_project_components(self, project): """ requires : project instance returns : list of components """ return [ comp.name for comp in self.jira_instance.project_components(project) ] def get_issue_detail(self, issue_key): """ .fields.summary -- summary .fields.votes.votes -- votes .fields.description .fields.comment : [{ .author.displayName .body }] """ return self.jira_instance.issue(issue_key) def get_issues_in_project(self, project_key): """ [ .fields.summary -- summary .fields.votes.votes -- votes .fields.description .fields.comment : [{ .author.displayName .body }] ] """ return self.jira_instance.search_issues(f"project={project_key}") def get_issues_of_current_user_in_project(self, project_key): """ [ .fields.summary -- summary .fields.votes.votes -- votes .fields.description .fields.comment : [{ .author.displayName .body }] ] """ return self.jira_instance.search_issues( f"project={project_key} and assignee=currentuser()") def get_issues_of_current_user(self): """ [ .fields.summary -- summary .fields.votes.votes -- votes .fields.description .fields.comment : [{ .author.displayName .body }] ] """ return self.jira_instance.search_issues(f"assignee=currentuser()") # jira = JIRA('https://anton3.atlassian.net',basic_auth=('*****@*****.**', 'TKLhcdc5zw2anmiX8DQ946BD')) # jra = jira.project('AN') # print(jra.name) # print(jra.lead.displayName) # email:[email protected] # password: pratikbaid@2471 # jira = AntonJira('https://anton3.atlassian.net','*****@*****.**', 'TKLhcdc5zw2anmiX8DQ946BD') # print(jira.get_issues_of_current_user('AN'))
dashboards = jira.dashboards() for dash in dashboards: print(dash.name, dash.id) cizo_dashboard = jira.dashboard(id=11105) # curago_dashboard = jira.dashboard('CURAGO') cizo_dash_page = requests.get(cizo_dashboard.view).text print(cizo_dash_page) for project in projects: print (project) cizo = jira.project('SEN') curago = jira.project('CUR') print ('Project: ', cizo.name) print ('Cizo Lead: ', cizo.lead.displayName) print ('==============================') print ('Project: ', curago.name) print ('Cizo Lead: ', curago.lead.displayName)
app = Flask(__name__) app.secret_key = os.urandom(24) CORS(app) chk2.initializeRoute(app) client = MongoClient('localhost', 27017) db = client.app #database post = db.posts #objects in database jira = JIRA('http://localhost:8080', auth=('Arijit82', 'admin')) projects = jira.projects() jra = jira.project('AR123') print(jra.name) # 'JIRA' print(jra.lead.displayName) print(projects) issue_dict = { 'project': { 'key': 'AR123' }, 'summary': 'New issue from jira-python', 'description': 'Look into this one', 'issuetype': { 'name': 'Task' }, }
class JiraTestManager(object): """ Used to instantiate and populate the JIRA instance with data used by the unit tests. Attributes: CI_JIRA_ADMIN (str): Admin user account name. CI_JIRA_USER (str): Limited user account name. max_retries (int): number of retries to perform for recoverable HTTP errors. """ __shared_state = {} def __init__(self): self.__dict__ = self.__shared_state if not self.__dict__: self.initialized = 0 try: if CI_JIRA_URL in os.environ: self.CI_JIRA_URL = os.environ['CI_JIRA_URL'] self.max_retries = 5 else: self.CI_JIRA_URL = "https://pycontribs.atlassian.net" self.max_retries = 5 if CI_JIRA_ADMIN in os.environ: self.CI_JIRA_ADMIN = os.environ['CI_JIRA_ADMIN'] else: self.CI_JIRA_ADMIN = 'ci-admin' if CI_JIRA_ADMIN_PASSWORD in os.environ: self.CI_JIRA_ADMIN_PASSWORD = os.environ[ 'CI_JIRA_ADMIN_PASSWORD'] else: self.CI_JIRA_ADMIN_PASSWORD = '******' if 'CI_JIRA_USER' in os.environ: self.CI_JIRA_USER = os.environ['CI_JIRA_USER'] else: self.CI_JIRA_USER = '******' if 'CI_JIRA_USER_PASSWORD' in os.environ: self.CI_JIRA_USER_PASSWORD = os.environ[ 'CI_JIRA_USER_PASSWORD'] else: self.CI_JIRA_USER_PASSWORD = '******' self.CI_JIRA_ISSUE = os.environ.get('CI_JIRA_ISSUE', 'Bug') if OAUTH: self.jira_admin = JIRA(oauth={ 'access_token': 'hTxcwsbUQiFuFALf7KZHDaeAJIo3tLUK', 'access_token_secret': 'aNCLQFP3ORNU6WY7HQISbqbhf0UudDAf', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}) else: if self.CI_JIRA_ADMIN: self.jira_admin = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_admin = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) if self.jira_admin.current_user() != self.CI_JIRA_ADMIN: # self.jira_admin. self.initialized = 1 sys.exit(3) if OAUTH: self.jira_sysadmin = JIRA(oauth={ 'access_token': '4ul1ETSFo7ybbIxAxzyRal39cTrwEGFv', 'access_token_secret': 'K83jBZnjnuVRcfjBflrKyThJa0KSjSs2', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}, logging=False, max_retries=self.max_retries) else: if self.CI_JIRA_ADMIN: self.jira_sysadmin = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_ADMIN, self.CI_JIRA_ADMIN_PASSWORD), logging=False, validate=True, max_retries=self.max_retries) else: self.jira_sysadmin = JIRA(self.CI_JIRA_URL, logging=False, max_retries=self.max_retries) if OAUTH: self.jira_normal = JIRA(oauth={ 'access_token': 'ZVDgYDyIQqJY8IFlQ446jZaURIz5ECiB', 'access_token_secret': '5WbLBybPDg1lqqyFjyXSCsCtAWTwz1eD', 'consumer_key': CONSUMER_KEY, 'key_cert': KEY_CERT_DATA}) else: if self.CI_JIRA_ADMIN: self.jira_normal = JIRA(self.CI_JIRA_URL, basic_auth=(self.CI_JIRA_USER, self.CI_JIRA_USER_PASSWORD), validate=True, logging=False, max_retries=self.max_retries) else: self.jira_normal = JIRA(self.CI_JIRA_URL, validate=True, logging=False, max_retries=self.max_retries) # now we need some data to start with for the tests # jira project key is max 10 chars, no letter. # [0] always "Z" # [1-6] username running the tests (hope we will not collide) # [7-8] python version A=0, B=1,.. # [9] A,B -- we may need more than one project prefix = 'Z' + (re.sub("[^A-Z]", "", getpass.getuser().upper()))[0:6] + \ chr(ord('A') + sys.version_info[0]) + \ chr(ord('A') + sys.version_info[1]) self.project_a = prefix + 'A' # old XSS self.project_a_name = "Test user=%s python=%s.%s A" \ % (getpass.getuser(), sys.version_info[0], sys.version_info[1]) self.project_b_name = "Test user=%s python=%s.%s B" \ % (getpass.getuser(), sys.version_info[0], sys.version_info[1]) self.project_b = prefix + 'B' # old BULK try: self.jira_admin.project(self.project_a) except Exception as e: logging.warning(e) pass else: self.jira_admin.delete_project(self.project_a) try: self.jira_admin.project(self.project_b) except Exception as e: logging.warning(e) pass else: self.jira_admin.delete_project(self.project_b) self.jira_admin.create_project(self.project_a, self.project_a_name) self.project_a_id = self.jira_admin.project(self.project_a).id self.jira_admin.create_project(self.project_b, self.project_b_name) self.project_b_issue1_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 1 from %s' % self.project_b, issuetype=self.CI_JIRA_ISSUE) self.project_b_issue1 = self.project_b_issue1_obj.key self.project_b_issue2_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 2 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue2 = self.project_b_issue2_obj.key self.project_b_issue3_obj = self.jira_admin.create_issue(project=self.project_b, summary='issue 3 from %s' % self.project_b, issuetype={'name': self.CI_JIRA_ISSUE}) self.project_b_issue3 = self.project_b_issue3_obj.key except Exception as e: # exc_type, exc_value, exc_traceback = sys.exc_info() formatted_lines = traceback.format_exc().splitlines() msg = "Basic test setup failed: %s\n\t%s" % ( e, "\n\t".join(formatted_lines)) logging.fatal(msg) self.initialized = 1 pytest.exit("FATAL") self.initialized = 1 else: # already exist but we need to be sure it was initialized counter = 0 while not self.initialized: sleep(1) counter += 1 if counter > 60: logging.fatal("Something is clearly not right with " + "initialization, killing the tests to prevent a " + "deadlock.") sys.exit(3)
def Update_TotalData_from_Jira(VDD): SheetNames = VDD.sheetnames TotalDataWS = VDD['Total Data'] NumRows = TotalDataWS.max_row NumColumn = TotalDataWS.max_column CRs = ['' for x in range(NumRows + 1)] newTotalData = VDD.copy_worksheet(TotalDataWS) newTotalData.title = 'New TD jira' AllM145Issues = VDD.create_sheet('AllM145') #for row in range(1,NumRows): for row in range(1, NumRows + 1): CRs[row] = str(TotalDataWS[row][8].value) # cleanup list of CRs so that only 1 CR per for row in range(1, len(CRs)): i = 0 for char in CRs[row]: if char == '\n': CRs[row] = CRs[row][:i] elif char == 'O' and CRs[row][i:i + 3] == 'OMS' and i > 3: CRs[row] = CRs[row][:i] elif char == 'I' and CRs[row][i:i + 4] == 'IFIS' and i > 3: CRs[row] = CRs[row][:i] elif char == 'E' and CRs[row][i:i + 5] == 'EICAS' and i > 3: CRs[row] = CRs[row][:i] elif char == 'F' and CRs[row][i:i + 4] == 'FDSA' and i > 3: CRs[row] = CRs[row][:i] i += 1 options = {'server': 'http://alm.rockwellcollins.com/issues/'} jira = JIRA(options, basic_auth=('jpgaviri', 'PSW*')) #Use RCI password # Get all projects viewable by anonymous users. projects = jira.projects() M145 = jira.project('GLOBALA') #M145_issues = jira.search_issues('project=GLOBALA') block_size = 50 block_num = 0 i = 1 while True: start_idx = block_num * block_size M145_issues = jira.search_issues('project=GLOBALA', start_idx, block_size) #--------------------- Wp_type, summary, description, function, fix_version, affected_projects,labels, \ Operational_impact,Plain_English,Criticality = '','','','','','','','','','' for issue in M145_issues: print(issue) Wp_type, summary, description, function, fix_version, affected_projects,labels, \ Operational_impact,Plain_English,Criticality = '','','','','','','','','','' #english_description = issue.raw['fields']['customfield_20330'] for field in issue.raw['fields']: if field == 'customfield_12909': if issue.raw['fields']['customfield_12909'] != None: Wp_type = issue.raw['fields']['customfield_12909'][ 'value'] if field == 'summary': if issue.raw['fields']['summary'] != None: summary = issue.raw['fields']['summary'] if field == 'description': if issue.raw['fields']['description'] != None: description = issue.raw['fields']['description'] if field == 'customfield_19307': if issue.raw['fields']['customfield_19307'] != None: function = issue.raw['fields']['customfield_19307'][ 'value'] if field == 'fixVersions': if issue.raw['fields']['fixVersions'] != []: fix_version = issue.raw['fields']['fixVersions'][0][ 'name'] if field == 'customfield_13705': if issue.raw['fields']['customfield_13705'] != None: affected_projects = issue.raw['fields'][ 'customfield_13705'] if field == 'labels': if issue.raw['fields']['labels'] != None: labels = issue.raw['fields']['labels'] if field == 'customfield_12906': if issue.raw['fields']['customfield_12906'] != None: Operational_impact = issue.raw['fields'][ 'customfield_12906'] if field == 'customfield_18527': if issue.raw['fields']['customfield_18527'] != None: Plain_English = issue.raw['fields'][ 'customfield_18527'] if field == 'customfield_13514': if issue.raw['fields']['customfield_13514'] != []: Criticality = issue.raw['fields']['customfield_13514'][ 0] #last_update = issue.raw['fields']['customfield_19063'] #print ("this seems to have worked") label = '' if labels != '': for l in labels: label = label + ', ' + str(l) label = str(label) AllM145Issues.cell(row=i, column=2) AllM145Issues.cell(row=i, column=3) AllM145Issues[i][0].value = 'CR NUmber: ' + str(issue.key) + '\n' \ 'Work Package Type: ' + str(Wp_type) + '\n' \ + 'Fix Version: '+ str(fix_version) +'\n' \ + 'Labels: '+ str(label) + '\n' \ + 'Summary: ' + str(summary) + '\n' \ + 'Operational Impact: '+ str(Operational_impact) +'\n' \ + 'English Description: '+ str(Plain_English) +'\n' \ + 'Description: ' + str(description) +'\n' AllM145Issues[i][1].value = str(issue.key) AllM145Issues[i][2].value = str(fix_version) i += 1 #-------------------- time.sleep(2) if len(M145_issues) == 0: # Retrieve issues until there are no more to come break block_num += 1 return VDD
class JiraAPI(object): def __init__(self, hostname=None, username=None, password=None, path="", debug=False, clean_obsolete=True, max_time_window=12): self.logger = logging.getLogger('JiraAPI') if debug: self.logger.setLevel(logging.DEBUG) if "https://" not in hostname: hostname = "https://{}".format(hostname) self.username = username self.password = password self.jira = JIRA(options={'server': hostname}, basic_auth=(self.username, self.password)) self.logger.info("Created vjira service for {}".format(hostname)) self.all_tickets = [] self.JIRA_REOPEN_ISSUE = "Reopen Issue" self.JIRA_CLOSE_ISSUE = "Close Issue" self.max_time_tracking = max_time_window #in months #<JIRA Resolution: name=u'Obsolete', id=u'11'> self.JIRA_RESOLUTION_OBSOLETE = "Obsolete" self.JIRA_RESOLUTION_FIXED = "Fixed" self.clean_obsolete = clean_obsolete self.template_path = 'vulnwhisp/reporting/resources/ticket.tpl' if path: self.download_tickets(path) else: self.logger.warn("No local path specified, skipping Jira ticket download.") def create_ticket(self, title, desc, project="IS", components=[], tags=[]): labels = ['vulnerability_management'] for tag in tags: labels.append(str(tag)) self.logger.info("creating ticket for project {} title[20] {}".format(project, title[:20])) self.logger.info("project {} has a component requirement: {}".format(project, self.PROJECT_COMPONENT_TABLE[project])) project_obj = self.jira.project(project) components_ticket = [] for component in components: exists = False for c in project_obj.components: if component == c.name: self.logger.debug("resolved component name {} to id {}".format(c.name, c.id)) components_ticket.append({ "id": c.id }) exists=True if not exists: self.logger.error("Error creating Ticket: component {} not found".format(component)) return 0 new_issue = self.jira.create_issue(project=project, summary=title, description=desc, issuetype={'name': 'Bug'}, labels=labels, components=components_ticket) self.logger.info("Ticket {} created successfully".format(new_issue)) return new_issue #Basic JIRA Metrics def metrics_open_tickets(self, project=None): jql = "labels= vulnerability_management and resolution = Unresolved" if project: jql += " and (project='{}')".format(project) self.logger.debug('Executing: {}'.format(jql)) return len(self.jira.search_issues(jql, maxResults=0)) def metrics_closed_tickets(self, project=None): jql = "labels= vulnerability_management and NOT resolution = Unresolved AND created >=startOfMonth(-{})".format(self.max_time_tracking) if project: jql += " and (project='{}')".format(project) return len(self.jira.search_issues(jql, maxResults=0)) def sync(self, vulnerabilities, project, components=[]): #JIRA structure of each vulnerability: [source, scan_name, title, diagnosis, consequence, solution, ips, risk, references] self.logger.info("JIRA Sync started") # [HIGIENE] close tickets older than 12 months as obsolete # Higiene clean up affects to all tickets created by the module, filters by label 'vulnerability_management' if self.clean_obsolete: self.close_obsolete_tickets() for vuln in vulnerabilities: # JIRA doesn't allow labels with spaces, so making sure that the scan_name doesn't have spaces # if it has, they will be replaced by "_" if " " in vuln['scan_name']: vuln['scan_name'] = "_".join(vuln['scan_name'].split(" ")) exists = False to_update = False ticketid = "" ticket_assets = [] exists, to_update, ticketid, ticket_assets = self.check_vuln_already_exists(vuln) if exists: # If ticket "resolved" -> reopen, as vulnerability is still existent self.reopen_ticket(ticketid) self.add_label(ticketid, vuln['risk']) continue elif to_update: self.ticket_update_assets(vuln, ticketid, ticket_assets) self.add_label(ticketid, vuln['risk']) continue try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception templating: {}'.format(str(e))) return 0 self.create_ticket(title=vuln['title'], desc=tpl, project=project, components=components, tags=[vuln['source'], vuln['scan_name'], 'vulnerability', vuln['risk']]) self.close_fixed_tickets(vulnerabilities) # we reinitialize so the next sync redoes the query with their specific variables self.all_tickets = [] return True def check_vuln_already_exists(self, vuln): # we need to return if the vulnerability has already been reported and the ID of the ticket for further processing #function returns array [duplicated(bool), update(bool), ticketid, ticket_assets] title = vuln['title'] labels = [vuln['source'], vuln['scan_name'], 'vulnerability_management', 'vulnerability'] #list(set()) to remove duplicates assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) if not self.all_tickets: self.logger.info("Retrieving all JIRA tickets with the following tags {}".format(labels)) # we want to check all JIRA tickets, to include tickets moved to other queues # will exclude tickets older than 12 months, old tickets will get closed for higiene and recreated if still vulnerable jql = "{} AND NOT labels=advisory AND created >=startOfMonth(-{})".format(" AND ".join(["labels={}".format(label) for label in labels]), self.max_time_tracking) self.all_tickets = self.jira.search_issues(jql, maxResults=0) #WARNING: function IGNORES DUPLICATES, after finding a "duplicate" will just return it exists #it wont iterate over the rest of tickets looking for other possible duplicates/similar issues self.logger.info("Comparing Vulnerabilities to created tickets") for index in range(len(self.all_tickets)-1): checking_ticketid, checking_title, checking_assets = self.ticket_get_unique_fields(self.all_tickets[index]) if title == checking_title: difference = list(set(assets).symmetric_difference(checking_assets)) #to check intersection - set(assets) & set(checking_assets) if difference: self.logger.info("Asset mismatch, ticket to update. Ticket ID: {}".format(checking_ticketid)) return False, True, checking_ticketid, checking_assets #this will automatically validate else: self.logger.info("Confirmed duplicated. TickedID: {}".format(checking_ticketid)) return True, False, checking_ticketid, [] #this will automatically validate return False, False, "", [] def ticket_get_unique_fields(self, ticket): title = ticket.raw.get('fields', {}).get('summary').encode("ascii").strip() ticketid = ticket.key.encode("ascii") try: affected_assets_section = ticket.raw.get('fields', {}).get('description').encode("ascii").split("{panel:title=Affected Assets}")[1].split("{panel}")[0] assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", affected_assets_section))) except: self.logger.error("Ticket IPs regex failed. Ticket ID: {}".format(ticketid)) assets = [] return ticketid, title, assets def get_ticket_reported_assets(self, ticket): #[METRICS] return a list with all the affected assets for that vulnerability (including already resolved ones) return list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b",str(self.jira.issue(ticket).raw)))) def get_resolution_time(self, ticket): #get time a ticket took to be resolved ticket_obj = self.jira.issue(ticket) if self.is_ticket_resolved(ticket_obj): ticket_data = ticket_obj.raw.get('fields') #dates follow format '2018-11-06T10:36:13.849+0100' created = [int(x) for x in ticket_data['created'].split('.')[0].replace('T', '-').replace(':','-').split('-')] resolved =[int(x) for x in ticket_data['resolutiondate'].split('.')[0].replace('T', '-').replace(':','-').split('-')] start = datetime(created[0],created[1],created[2],created[3],created[4],created[5]) end = datetime(resolved[0],resolved[1],resolved[2],resolved[3],resolved[4],resolved[5]) return (end-start).days else: self.logger.error("Ticket {ticket} is not resolved, can't calculate resolution time".format(ticket=ticket)) return False def ticket_update_assets(self, vuln, ticketid, ticket_assets): # correct description will always be in the vulnerability to report, only needed to update description to new one self.logger.info("Ticket {} exists, UPDATE requested".format(ticketid)) if self.is_ticket_resolved(self.jira.issue(ticketid)): self.reopen_ticket(ticketid) try: tpl = template(self.template_path, vuln) except Exception as e: self.logger.error('Exception updating assets: {}'.format(str(e))) return 0 ticket_obj = self.jira.issue(ticketid) ticket_obj.update() assets = list(set(re.findall(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ",".join(vuln['ips'])))) difference = list(set(assets).symmetric_difference(ticket_assets)) comment = '' #put a comment with the assets that have been added/removed for asset in difference: if asset in assets: comment += "Asset {} have been added to the ticket as vulnerability *has been newly detected*.\n".format(asset) elif asset in ticket_assets: comment += "Asset {} have been removed from the ticket as vulnerability *has been resolved*.\n".format(asset) try: ticket_obj.update(description=tpl, comment=comment, fields={"labels":ticket_obj.fields.labels}) self.logger.info("Ticket {} updated successfully".format(ticketid)) self.add_label(ticketid, 'updated') except: self.logger.error("Error while trying up update ticket {}".format(ticketid)) return 0 def add_label(self, ticketid, label): ticket_obj = self.jira.issue(ticketid) if label not in ticket_obj.fields.labels: ticket_obj.fields.labels.append(label) try: ticket_obj.update(fields={"labels":ticket_obj.fields.labels}) self.logger.info("Added label {label} to ticket {ticket}".format(label=label, ticket=ticketid)) except: self.logger.error("Error while trying to add label {label} to ticket {ticket}".format(label=label, ticket=ticketid)) return 0 def close_fixed_tickets(self, vulnerabilities): # close tickets which vulnerabilities have been resolved and are still open found_vulns = [] for vuln in vulnerabilities: found_vulns.append(vuln['title']) comment = '''This ticket is being closed as it appears that the vulnerability no longer exists. If the vulnerability reappears, a new ticket will be opened.''' for ticket in self.all_tickets: if ticket.raw['fields']['summary'].strip() in found_vulns: self.logger.info("Ticket {} is still vulnerable".format(ticket)) continue self.logger.info("Ticket {} is no longer vulnerable".format(ticket)) self.close_ticket(ticket, self.JIRA_RESOLUTION_FIXED, comment) return 0 def is_ticket_reopenable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_REOPEN_ISSUE: self.logger.debug("Ticket is reopenable") return True self.logger.warn("Ticket can't be opened. Check Jira transitions.") return False def is_ticket_closeable(self, ticket_obj): transitions = self.jira.transitions(ticket_obj) for transition in transitions: if transition.get('name') == self.JIRA_CLOSE_ISSUE: return True self.logger.warn("Ticket can't closed. Check Jira transitions.") return False def is_ticket_resolved(self, ticket_obj): #Checks if a ticket is resolved or not if ticket_obj is not None: if ticket_obj.raw['fields'].get('resolution') is not None: if ticket_obj.raw['fields'].get('resolution').get('name') != 'Unresolved': self.logger.debug("Checked ticket {} is already closed".format(ticket_obj)) self.logger.info("Ticket {} is closed".format(ticket_obj)) return True self.logger.debug("Checked ticket {} is already open".format(ticket_obj)) return False def is_risk_accepted(self, ticket_obj): if ticket_obj is not None: if ticket_obj.raw['fields'].get('labels') is not None: labels = ticket_obj.raw['fields'].get('labels') if "risk_accepted" in labels: self.logger.warn("Ticket {} accepted risk, will be ignored".format(ticket_obj)) return True elif "server_decomission" in labels: self.logger.warn("Ticket {} server decomissioned, will be ignored".format(ticket_obj)) return True self.logger.info("Ticket {} risk has not been accepted".format(ticket_obj)) return False def reopen_ticket(self, ticketid): self.logger.debug("Ticket {} exists, REOPEN requested".format(ticketid)) # this will reopen a ticket by ticketid ticket_obj = self.jira.issue(ticketid) if self.is_ticket_resolved(ticket_obj): if not self.is_risk_accepted(ticket_obj): try: if self.is_ticket_reopenable(ticket_obj): comment = '''This ticket has been reopened due to the vulnerability not having been fixed (if multiple assets are affected, all need to be fixed; if the server is down, lastest known vulnerability might be the one reported). In the case of the team accepting the risk and wanting to close the ticket, please add the label "*risk_accepted*" to the ticket before closing it. If server has been decomissioned, please add the label "*server_decomission*" to the ticket before closing it. If you have further doubts, please contact the Security Team.''' error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_REOPEN_ISSUE, comment = comment) self.logger.info("Ticket {} reopened successfully".format(ticketid)) self.add_label(ticketid, 'reopened') return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error reopening ticket {}: {}".format(ticketid, e)) return 0 return 0 def close_ticket(self, ticketid, resolution, comment): # this will close a ticket by ticketid self.logger.debug("Ticket {} exists, CLOSE requested".format(ticketid)) ticket_obj = self.jira.issue(ticketid) if not self.is_ticket_resolved(ticket_obj): try: if self.is_ticket_closeable(ticket_obj): #need to add the label before closing the ticket self.add_label(ticketid, 'closed') error = self.jira.transition_issue(issue=ticketid, transition=self.JIRA_CLOSE_ISSUE, comment = comment, resolution = {"name": resolution }) self.logger.info("Ticket {} closed successfully".format(ticketid)) return 1 except Exception as e: # continue with ticket data so that a new ticket is created in place of the "lost" one self.logger.error("error closing ticket {}: {}".format(ticketid, e)) return 0 return 0 def close_obsolete_tickets(self): # Close tickets older than 12 months, vulnerabilities not solved will get created a new ticket self.logger.info("Closing obsolete tickets older than {} months".format(self.max_time_tracking)) jql = "labels=vulnerability_management AND created <startOfMonth(-{}) and resolution=Unresolved".format(self.max_time_tracking) tickets_to_close = self.jira.search_issues(jql, maxResults=0) comment = '''This ticket is being closed for hygiene, as it is more than 12 months old. If the vulnerability still exists, a new ticket will be opened.''' for ticket in tickets_to_close: self.close_ticket(ticket, self.JIRA_RESOLUTION_OBSOLETE, comment) return 0 def project_exists(self, project): try: self.jira.project(project) return True except: return False return False def download_tickets(self, path): #saves all tickets locally, local snapshot of vulnerability_management ticktes #check if file already exists check_date = str(date.today()) fname = '{}jira_{}.json'.format(path, check_date) if os.path.isfile(fname): self.logger.info("File {} already exists, skipping ticket download".format(fname)) return True try: self.logger.info("Saving locally tickets from the last {} months".format(self.max_time_tracking)) jql = "labels=vulnerability_management AND created >=startOfMonth(-{})".format(self.max_time_tracking) tickets_data = self.jira.search_issues(jql, maxResults=0) #end of line needed, as writelines() doesn't add it automatically, otherwise one big line to_save = [json.dumps(ticket.raw.get('fields'))+"\n" for ticket in tickets_data] with open(fname, 'w') as outfile: outfile.writelines(to_save) self.logger.info("Tickets saved succesfully.") return True except Exception as e: self.logger.error("Tickets could not be saved locally: {}.".format(e)) return False