Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redmine responder #342

Merged
merged 6 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions responders/Redmine/Redmine_Issue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"name": "Redmine_Issue",
"version": "1.0",
"author": "Marc-André DOLL",
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
"license": "AGPL-V3",
"description": "Create a redmine issue from a case",
"dataTypeList": [
"thehive:case",
"thehive:case_task"
],
"command": "Redmine/redmine.py",
"baseConfig": "Redmine",
"configurationItems": [
{
"name": "instance_name",
"description": "Name of the Redmine instance",
"multi": false,
"required": false,
"type": "string",
"defaultValue": "redmine"
},
{
"name": "url",
"description": "URL where to find the Redmine API",
"type": "string",
"multi": false,
"required": true
},
{
"name": "username",
"description": "Username to log into Redmine",
"type": "string",
"multi": false,
"required": true
},
{
"name": "password",
"description": "Password to log into Redmine",
"type": "string",
"multi": false,
"required": true
},
{
"name": "project_field",
"description": "Name of the custom field containing the Redmine project to use when creating the issue",
"multi": false,
"required": true,
"type": "string"
},
{
"name": "tracker_field",
"description": "Name of the custom field containing the Redmine tracker to use when creating the issue",
"multi": false,
"required": true,
"type": "string"
},
{
"name": "assignee_field",
"description": "Name of the custom field containing the Redmine assignee to use when creating the issue",
"multi": false,
"required": false,
"type": "string"
},
{
"name": "reference_field",
"description": "Name of the case custom field in which to store the opened issue. If not defined, this information will not be stored",
"type": "string",
"required": false,
"multi": false
},
{
"name": "opening_status",
"description": "Status used when opening a Redmine issue (if not defined here, will use the default opening status from the Redmine Workflow)",
"type": "string",
"multi": false,
"required": false
},
{
"name": "closing_task",
"description": "Closing the task after successfully creating the Redmine issue",
"type": "boolean",
"multi": false,
"defaultValue": false,
"required": false
}
]
}
67 changes: 67 additions & 0 deletions responders/Redmine/redmine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
# encoding: utf-8

from cortexutils.responder import Responder
import redmine_client

class Redmine(Responder):
def __init__(self):
Responder.__init__(self)
self.instance_name = self.get_param('config.instance_name', 'redmine')
self.instance_url = self.get_param('config.url', None, 'Missing Redmine URL')
self.client = redmine_client.RedmineClient(
baseurl=self.instance_url,
username=self.get_param('config.username', None, 'Missing username'),
password=self.get_param('config.password', None, 'Missing password'))
self.project_field = self.get_param('config.project_field', None, 'Missing custom field for Redmine project')
self.tracker_field = self.get_param('config.tracker_field', None, 'Missing custom field for Redmine tracker')
self.assignee_field = self.get_param('config.assignee_field', None, 'Missing custom field for Redmine assignee')
self.reference_field = self.get_param('config.reference_field', None)
self.closing_task = self.get_param('config.closing_task', False)

def run(self):
issue_data = {}
if self.data_type == 'thehive:case':
issue_data = self.extract_case_data()
elif self.data_type == 'thehive:case_task':
issue_data = self.extract_case_data('data.case')
else:
self.error('Invalid dataType')
try:
issue = self.client.create_issue(
title=issue_data['title'], body=issue_data['description'],
project=issue_data['project'], tracker=issue_data['tracker'],
status=issue_data['status'], priority=issue_data['severity'],
assignee=issue_data['assignee'])
self.report({
'message': 'issue {} created'.format(issue['issue']['id']),
'instance': {'name': self.instance_name, "url": self.instance_url},
'issue': issue
})
except Exception as e:
self.error(str(e))

def operations(self, raw):
ops = []
if self.reference_field:
ops.append(self.build_operation('AddCustomFields', name=self.reference_field, tpe='string', value='{}#{}'.format(self.instance_name, raw['issue']['issue']['id'])))
if self.data_type == 'thehive:case_task' and self.closing_task:
ops.append(self.build_operation('CloseTask'))
return ops

def extract_case_data(self, data_root='data'):
issue_data = {}
issue_data['title'] = self.get_param('{}.title'.format(data_root), None, 'Case title is missing')
issue_data['description'] = self.get_param('{}.description'.format(data_root), None, 'Case description is missing')
issue_data['severity'] = self.get_param('{}.severity'.format(data_root))
if self.project_field:
issue_data['project'] = self.get_param('{}.customFields.{}.string'.format(data_root, self.project_field), None, 'Project not defined in case')
if self.tracker_field:
issue_data['tracker'] = self.get_param('{}.customFields.{}.string'.format(data_root, self.tracker_field), None, 'Tracker not defined in case')
if self.assignee_field:
issue_data['assignee'] = self.get_param('{}.customFields.{}.string'.format(data_root, self.assignee_field), None)
issue_data['status'] = self.get_param('config.opening_status')
return issue_data

if __name__ == '__main__':
Redmine().run()
77 changes: 77 additions & 0 deletions responders/Redmine/redmine_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import requests

class RedmineClient:
def __init__(self, baseurl, username, password):
self.base_url = baseurl
self.session = requests.Session()
self.session.headers.update({'content-type': 'application/json'})
self.session.auth = (username, password)

def create_issue(self, title=None, body=None, project=None, tracker=None,
priority=None, status=None, assignee=None):
payload = {
'issue': {
'subject': title,
'description': body,
'project_id': project,
'tracker_id': self.get_tracker_id(tracker),
'priority_id': priority,
'status_id': self.get_status_id(status),
'assigned_to_id': self.get_assignee_id(project, assignee)
}
}
url = self.base_url + '/issues.json'
response = self.session.post(url, json=payload)
response.raise_for_status()
result = response.json()
if 'error' in result:
raise RedmineClientError(result['error'])
return result

def get_tracker_id(self, name):
url = self.base_url + '/trackers.json'
id = None
trackers = self.session.get(url)
trackers.raise_for_status()
for p in trackers.json()['trackers']:
if p['name'] == name:
id = p['id']
break
return id

def get_status_id(self, name):
url = self.base_url + '/issue_statuses.json'
id = None
issue_statuses = self.session.get(url)
issue_statuses.raise_for_status()
for p in issue_statuses.json()['issue_statuses']:
if p['name'] == name:
id = p['id']
break
return id

def get_assignee_id(self, project, assignee):
url = '{}/projects/{}/memberships.json'.format(self.base_url, project)
id = None
payload = {'offset': 0}
total_count = 0
while id is None and payload['offset'] <= total_count:
response = self.session.get(url, params=payload)
response.raise_for_status()
for member in response.json()['memberships']:
if 'user' in member:
if assignee == member['user']['name']:
id = member['user']['id']
break
elif 'group' in member:
if assignee == member['group']['name']:
id = member['group']['id']
break
total_count = response.json()['total_count']
payload['offset'] += response.json()['limit']
return id


class RedmineClientError(Exception):
def __init__(self, message):
self.message = message
2 changes: 2 additions & 0 deletions responders/Redmine/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests
cortexutils