Skip to content

Commit f5c9067

Browse files
authored
Tickets API (#975)
* Adding Ticket API * Adding tests for tickets API. Minor changes to tickets.py * Adjust testing framework to python responses instead of pytest-vcr. Sonar suggestion for dict to literal. * addressing pylint findings
1 parent b9dfcfd commit f5c9067

File tree

3 files changed

+467
-1
lines changed

3 files changed

+467
-1
lines changed

tenable/sc/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
scans
4545
status
4646
system
47+
tickets
4748
users
4849
4950
"""
@@ -83,6 +84,7 @@
8384
from .scans import ScanAPI
8485
from .status import StatusAPI
8586
from .system import SystemAPI
87+
from .tickets import TicketAPI
8688
from .users import UserAPI
8789

8890
try:
@@ -624,7 +626,15 @@ def system(self):
624626
:doc:`Tenable Security Center System APIs <system>`.
625627
"""
626628
return SystemAPI(self)
627-
629+
630+
@property
631+
def tickets(self):
632+
'''
633+
The interface object for the
634+
:doc:`Tenable Security Center Ticket APIs <system>`.
635+
'''
636+
return TicketAPI(self)
637+
628638
@property
629639
def users(self):
630640
"""

tenable/sc/tickets.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
'''
2+
tickets
3+
=====
4+
The following methods allow for interaction into the Tenable.sc
5+
:sc-api:`Ticket <Ticket.html>` API. These items are typically seen under the
6+
Worklow --> Tickets section of Tenable.sc.
7+
Methods available on ``sc.tickets``:
8+
.. rst-class:: hide-signature
9+
.. autoclass:: TicketAPI
10+
.. automethod:: create
11+
.. automethod:: details
12+
.. automethod:: edit
13+
.. automethod:: list
14+
Note: you cannot delete tickets, must set them to resolved and they will be auto-purged via system configured retention period
15+
'''
16+
from .base import SCEndpoint
17+
18+
class TicketAPI(SCEndpoint):
19+
def _constructor(self, **kw):
20+
'''
21+
Handles parsing the keywords and returns a ticket document
22+
'''
23+
24+
# Validate as dict and pass to assignee
25+
if 'assignee' in kw:
26+
kw['assignee'] = self._check('assignee', kw['assignee'], dict)
27+
28+
# all of the following keys are string values and do not require any
29+
# case conversion. We will simply iterate through them and verify that
30+
# they are in fact strings.
31+
keys = ['name', 'description', 'notes']
32+
for k in keys:
33+
if k in kw:
34+
self._check(k, kw[k], str)
35+
36+
if 'classification' in kw:
37+
# Verify that classification is one of the correct possible values.
38+
kw['classification'] = self._check(
39+
'classification',
40+
kw['classification'],
41+
str,
42+
choices=[
43+
'Information',
44+
'Configuration',
45+
'Patch',
46+
'Disable',
47+
'Firewall',
48+
'Schedule',
49+
'IDS',
50+
'Other',
51+
'Accept Risk',
52+
'Recast Risk',
53+
'Re-scan Request',
54+
'False Positive',
55+
'System Probe',
56+
'External Probe',
57+
'Investigation Needed',
58+
'Compromised System',
59+
'Virus Incident',
60+
'Bad Credentials',
61+
'Unauthorized Software',
62+
'Unauthorized System',
63+
'Unauthorized User'
64+
]
65+
)
66+
67+
if 'status' in kw:
68+
# Verify that status is one of the correct possible values
69+
kw['status'] = self._check(
70+
'status',
71+
kw['status'],
72+
str,
73+
choices=[
74+
'Assigned',
75+
'Resolved',
76+
'More Information',
77+
'Not Applicable',
78+
'Duplicate',
79+
'Closed'
80+
]
81+
)
82+
return kw
83+
84+
def create(self, name, assignee, **kw):
85+
'''
86+
Creates a ticket.
87+
:sc-api:`ticket: create <Ticket.html#ticket_POST>`
88+
Args:
89+
name (str):
90+
Required: The name for the ticket
91+
assignee (dict):
92+
Required: A dictionary containing one key (id) for the user the ticket is being assigned to
93+
status (str, optional):
94+
Optional status of the ticket: assigned, resolved, etc.
95+
classification (str, optional):
96+
Optional classification of the ticket type, i.e. Information, Other, etc.
97+
description (str, optional):
98+
Optional description for the ticket
99+
notes (str, optional):
100+
Optional notes associated with the ticket
101+
queries (list, optional):
102+
Optional list of IDs of queries to associate with the ticket
103+
query (object, optional):
104+
Optional query object
105+
Returns:
106+
:obj:`dict`:
107+
The newly created ticket.
108+
Examples:
109+
>>> ticket = sc.tickets.create('Example Ticket', {'id':1}, status='assigned', classification='information', description='This is an example ticket', notes='Example notes')
110+
'''
111+
kw['name'] = name
112+
kw['assignee'] = assignee
113+
payload = self._constructor(**kw)
114+
return self._api.post('ticket', json=payload).json()['response']
115+
116+
def details(self, id, fields=None):
117+
'''
118+
Returns the details for a specific ticket.
119+
:sc-api:`ticket: details <Ticket.html#TicketRESTReference-/ticket/{id}>`
120+
Args:
121+
id (int):
122+
Required: the unique identifier for the ticket to be returned
123+
fields (list):
124+
An optional list of attributes to return.
125+
Returns:
126+
:obj:`dict`:
127+
The ticket resource record.
128+
Examples:
129+
>>> ticket = sc.tickets.details(1)
130+
>>> pprint(ticket)
131+
'''
132+
params = {}
133+
if fields:
134+
params['fields'] = ','.join([self._check('field', f, str) for f in fields])
135+
ticket_id = self._check('id', id, int)
136+
return self._api.get(f'ticket/{ticket_id}', params=params).json()['response']
137+
138+
def edit(self, id, **kw):
139+
'''
140+
Edits a ticket.
141+
:sc-api:`ticket: edit <Ticket.html#ticket_id_PATCH>`
142+
Args:
143+
id (int):
144+
Required: unique identifier of the ticket to be edited
145+
name (str):
146+
Optional name for the ticket. Must not be blank.
147+
assignee (dict):
148+
Optional dictionary containing one key (id) for the user the ticket is being assigned to
149+
status (str, optional):
150+
Optional status of the ticket: assigned, resolved, etc. Must not be blank.
151+
classification (str, optional):
152+
Optional classification of the ticket type, i.e. Information, Other, etc. Must not be blank.
153+
description (str, optional):
154+
Optional description for the ticket
155+
notes (str, optional):
156+
Optional notes associated with the ticket
157+
queries (list, optional):
158+
Optional list of IDs of queries to associate with the ticket
159+
query (object, optional):
160+
Optional query object
161+
162+
Returns:
163+
:obj:`dict`:
164+
The newly updated ticket.
165+
Examples:
166+
>>> ticket = sc.tickets.edit(1, status='Resolved', notes='ran updates')
167+
'''
168+
payload = self._constructor(**kw)
169+
ticket_id = self._check('id', id, int)
170+
return self._api.patch(f'ticket/{ticket_id}', json=payload).json()['response']
171+
172+
def list(self, fields=None):
173+
'''
174+
Outputs a dictionary of usable and manageable tickets, within which is a list of tickets.
175+
:sc-api:`ticket: list <Ticket.html#ticket_GET>`
176+
Args:
177+
fields (list):
178+
Optional list of attributes to return for each ticket, e.g. ["name","description"]. If not specified, only a list of ticket IDs will return
179+
Returns:
180+
:obj:`dict`:
181+
A dictionary with two lists of ticket resources.
182+
Examples:
183+
>>> for ticket in sc.tickets.list():
184+
... pprint(ticket)
185+
'''
186+
params = {}
187+
if fields:
188+
params['fields'] = ','.join([self._check('field', f, str)
189+
for f in fields])
190+
191+
return self._api.get('ticket', params=params).json()['response']

0 commit comments

Comments
 (0)