1# -*- coding: utf-8 -*- 2# Copyright (C) 2021 Greenbone Networks GmbH 3# 4# SPDX-License-Identifier: GPL-3.0-or-later 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19from enum import Enum 20 21from typing import Any, Optional 22 23from gvm.errors import InvalidArgument, InvalidArgumentType, RequiredArgument 24from gvm.utils import add_filter, to_bool 25from gvm.xml import XmlCommand 26 27 28class TicketStatus(Enum): 29 """Enum for ticket status""" 30 31 OPEN = 'Open' 32 FIXED = 'Fixed' 33 CLOSED = 'Closed' 34 35 @classmethod 36 def from_string( 37 cls, 38 ticket_status: Optional[str], 39 ) -> Optional["TicketStatus"]: 40 """Convert a ticket status string into a TicketStatus instance""" 41 if not ticket_status: 42 return None 43 44 try: 45 return cls[ticket_status.upper()] 46 except KeyError: 47 raise InvalidArgument( 48 argument='ticket_status', 49 function=cls.from_string.__name__, 50 ) from None 51 52 53class TicketsMixin: 54 def clone_ticket(self, ticket_id: str) -> Any: 55 """Clone an existing ticket 56 57 Arguments: 58 ticket_id: UUID of an existing ticket to clone from 59 60 Returns: 61 The response. See :py:meth:`send_command` for details. 62 """ 63 if not ticket_id: 64 raise RequiredArgument( 65 function=self.clone_ticket.__name__, argument='ticket_id' 66 ) 67 68 cmd = XmlCommand("create_ticket") 69 70 _copy = cmd.add_element("copy", ticket_id) 71 72 return self._send_xml_command(cmd) 73 74 def create_ticket( 75 self, 76 *, 77 result_id: str, 78 assigned_to_user_id: str, 79 note: str, 80 comment: Optional[str] = None, 81 ) -> Any: 82 """Create a new ticket 83 84 Arguments: 85 result_id: UUID of the result the ticket applies to 86 assigned_to_user_id: UUID of a user the ticket should be assigned to 87 note: A note about opening the ticket 88 comment: Comment for the ticket 89 90 Returns: 91 The response. See :py:meth:`send_command` for details. 92 """ 93 if not result_id: 94 raise RequiredArgument( 95 function=self.create_ticket.__name__, argument='result_id' 96 ) 97 98 if not assigned_to_user_id: 99 raise RequiredArgument( 100 function=self.create_ticket.__name__, 101 argument='assigned_to_user_id', 102 ) 103 104 if not note: 105 raise RequiredArgument( 106 function=self.create_ticket.__name__, argument='note' 107 ) 108 109 cmd = XmlCommand("create_ticket") 110 111 _result = cmd.add_element("result") 112 _result.set_attribute("id", result_id) 113 114 _assigned = cmd.add_element("assigned_to") 115 _user = _assigned.add_element("user") 116 _user.set_attribute("id", assigned_to_user_id) 117 118 _note = cmd.add_element("open_note", note) 119 120 if comment: 121 cmd.add_element("comment", comment) 122 123 return self._send_xml_command(cmd) 124 125 def delete_ticket( 126 self, ticket_id: str, *, ultimate: Optional[bool] = False 127 ): 128 """Deletes an existing ticket 129 130 Arguments: 131 ticket_id: UUID of the ticket to be deleted. 132 ultimate: Whether to remove entirely, or to the trashcan. 133 """ 134 if not ticket_id: 135 raise RequiredArgument( 136 function=self.delete_ticket.__name__, argument='ticket_id' 137 ) 138 139 cmd = XmlCommand("delete_ticket") 140 cmd.set_attribute("ticket_id", ticket_id) 141 cmd.set_attribute("ultimate", to_bool(ultimate)) 142 143 return self._send_xml_command(cmd) 144 145 def get_tickets( 146 self, 147 *, 148 trash: Optional[bool] = None, 149 filter_string: Optional[str] = None, 150 filter_id: Optional[str] = None, 151 ) -> Any: 152 """Request a list of tickets 153 154 Arguments: 155 filter_string: Filter term to use for the query 156 filter_id: UUID of an existing filter to use for the query 157 trash: True to request the tickets in the trashcan 158 159 Returns: 160 The response. See :py:meth:`send_command` for details. 161 """ 162 cmd = XmlCommand("get_tickets") 163 164 add_filter(cmd, filter_string, filter_id) 165 166 if trash is not None: 167 cmd.set_attribute("trash", to_bool(trash)) 168 169 return self._send_xml_command(cmd) 170 171 def get_ticket(self, ticket_id: str) -> Any: 172 """Request a single ticket 173 174 Arguments: 175 ticket_id: UUID of an existing ticket 176 177 Returns: 178 The response. See :py:meth:`send_command` for details. 179 """ 180 if not ticket_id: 181 raise RequiredArgument( 182 function=self.get_ticket.__name__, argument='ticket_id' 183 ) 184 185 cmd = XmlCommand("get_tickets") 186 cmd.set_attribute("ticket_id", ticket_id) 187 return self._send_xml_command(cmd) 188 189 def modify_ticket( 190 self, 191 ticket_id: str, 192 *, 193 status: Optional[TicketStatus] = None, 194 note: Optional[str] = None, 195 assigned_to_user_id: Optional[str] = None, 196 comment: Optional[str] = None, 197 ) -> Any: 198 """Modify a single ticket 199 200 Arguments: 201 ticket_id: UUID of an existing ticket 202 status: New status for the ticket 203 note: Note for the status change. Required if status is set. 204 assigned_to_user_id: UUID of the user the ticket should be assigned 205 to 206 comment: Comment for the ticket 207 208 Returns: 209 The response. See :py:meth:`send_command` for details. 210 """ 211 if not ticket_id: 212 raise RequiredArgument( 213 function=self.modify_ticket.__name__, argument='ticket_id' 214 ) 215 216 if status and not note: 217 raise RequiredArgument( 218 function=self.modify_ticket.__name__, argument='note' 219 ) 220 221 if note and not status: 222 raise RequiredArgument( 223 function=self.modify_ticket.__name__, argument='status' 224 ) 225 226 cmd = XmlCommand("modify_ticket") 227 cmd.set_attribute("ticket_id", ticket_id) 228 229 if assigned_to_user_id: 230 _assigned = cmd.add_element("assigned_to") 231 _user = _assigned.add_element("user") 232 _user.set_attribute("id", assigned_to_user_id) 233 234 if status: 235 if not isinstance(status, TicketStatus): 236 raise InvalidArgumentType( 237 function=self.modify_ticket.__name__, 238 argument='status', 239 arg_type=TicketStatus.__name__, 240 ) 241 242 cmd.add_element('status', status.value) 243 cmd.add_element(f'{status.name.lower()}_note', note) 244 245 if comment: 246 cmd.add_element("comment", comment) 247 248 return self._send_xml_command(cmd) 249