nexpose_ticketing 1.0.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +22 -0
 - data/bin/nexpose_ticketing +45 -0
 - data/lib/nexpose_ticketing/config/jira.config +5 -1
 - data/lib/nexpose_ticketing/config/remedy.config +4 -1
 - data/lib/nexpose_ticketing/config/servicedesk.config +4 -1
 - data/lib/nexpose_ticketing/config/servicenow.config +4 -1
 - data/lib/nexpose_ticketing/config/ticket_service.config +13 -5
 - data/lib/nexpose_ticketing/helpers/base_helper.rb +43 -0
 - data/lib/nexpose_ticketing/helpers/jira_helper.rb +149 -97
 - data/lib/nexpose_ticketing/helpers/remedy_helper.rb +37 -52
 - data/lib/nexpose_ticketing/helpers/servicedesk_helper.rb +30 -49
 - data/lib/nexpose_ticketing/helpers/servicenow_helper.rb +39 -56
 - data/lib/nexpose_ticketing/modes/base_mode.rb +199 -0
 - data/lib/nexpose_ticketing/modes/default_mode.rb +50 -0
 - data/lib/nexpose_ticketing/modes/ip_mode.rb +52 -0
 - data/lib/nexpose_ticketing/modes/vulnerability_mode.rb +50 -0
 - data/lib/nexpose_ticketing/nx_logger.rb +44 -33
 - data/lib/nexpose_ticketing/queries.rb +30 -27
 - data/lib/nexpose_ticketing/report_helper.rb +1 -0
 - data/lib/nexpose_ticketing/ticket_metrics.rb +39 -0
 - data/lib/nexpose_ticketing/ticket_repository.rb +65 -206
 - data/lib/nexpose_ticketing/ticket_service.rb +470 -441
 - data/lib/nexpose_ticketing/version.rb +1 -1
 - metadata +15 -16
 - data/bin/nexpose_jira +0 -27
 - data/bin/nexpose_remedy +0 -27
 - data/bin/nexpose_servicedesk +0 -27
 - data/bin/nexpose_servicenow +0 -27
 - data/lib/nexpose_ticketing/common_helper.rb +0 -344
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 03778e753df032041d1c6a46bcef5f89df66d705
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: d163c3488d78c12ade992381b06479ba8ad40be9
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 958e7e85146d2c2010b10723b06570203effb135017b3f280d8132d60fe5903d73b713a4d3bf9356f9dc63881067b52f8dde20e72546bad1565857bedb2284ae
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ebaaa9c06a62105e3cb00425f1cc7e2ed93f67d2103f8224488da3f673b226dfd73abce6a72bb389869b6aa5bb4fcafad25167356167c0ef044b7f8018c61e08
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -72,6 +72,28 @@ We welcome contributions to this package. We ask only that pull requests and pat 
     | 
|
| 
       72 
72 
     | 
    
         | 
| 
       73 
73 
     | 
    
         
             
            ##Changelog
         
     | 
| 
       74 
74 
     | 
    
         | 
| 
      
 75 
     | 
    
         
            +
            ###1.2.0
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            ####Configuration Options
         
     | 
| 
      
 78 
     | 
    
         
            +
            Ticketing mode must be specified using the entire title, rather than a single character. e.g. 'Vulnerability' instead of 'V'
         
     | 
| 
      
 79 
     | 
    
         
            +
            Added the following configuration option:
         
     | 
| 
      
 80 
     | 
    
         
            +
            - log_console - NXLogger also gets printed to the console.
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
            ####Ticketing Modes
         
     | 
| 
      
 83 
     | 
    
         
            +
            Ticketing modes (Default, IP, Vulnerability) are now abstracted into their own classes.
         
     | 
| 
      
 84 
     | 
    
         
            +
            CommonHelper has been replaced with BaseMode from which other modes are extended.
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
            ####Ticketing Helpers
         
     | 
| 
      
 87 
     | 
    
         
            +
            Ticketing helpers extend a BaseHelper class.
         
     | 
| 
      
 88 
     | 
    
         
            +
            Ticketing helpers now log the number of tickets that are opened/closed/updated.
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
            ###1.1.0
         
     | 
| 
      
 91 
     | 
    
         
            +
            10-02-2016
         
     | 
| 
      
 92 
     | 
    
         
            +
            Added the following configuration options:
         
     | 
| 
      
 93 
     | 
    
         
            +
            - max_ticket_length - Specifies a maximum length for the description field of a ticket.
         
     | 
| 
      
 94 
     | 
    
         
            +
            - max_title_length - Specifies a maximum length for the title of a ticket.
         
     | 
| 
      
 95 
     | 
    
         
            +
            - max_num_refs - Specifies the maximum number of references included in a vulnerability description.
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
       75 
97 
     | 
    
         
             
            ###1.0.2
         
     | 
| 
       76 
98 
     | 
    
         
             
            08-02-2016
         
     | 
| 
       77 
99 
     | 
    
         
             
            - Encoding is now enforced as UTF-8 when parsing CSV files - fixes environment-specific errors.
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'nexpose_ticketing'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'nexpose_ticketing/nx_logger'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'nexpose_ticketing/version'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            options = {}
         
     | 
| 
      
 9 
     | 
    
         
            +
            OptionParser.new do |opts|
         
     | 
| 
      
 10 
     | 
    
         
            +
              opts.banner = "Usage: nexpose_ticketing [service name]"
         
     | 
| 
      
 11 
     | 
    
         
            +
            end.parse!
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            if ARGV.count == 0
         
     | 
| 
      
 14 
     | 
    
         
            +
              puts 'Ticketing system name required.'
         
     | 
| 
      
 15 
     | 
    
         
            +
              exit -1
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            system = ARGV.first
         
     | 
| 
      
 19 
     | 
    
         
            +
            config_path = File.join(File.dirname(__FILE__),
         
     | 
| 
      
 20 
     | 
    
         
            +
                                    "../lib/nexpose_ticketing/config/#{system}.config")
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            unless File.exists? config_path
         
     | 
| 
      
 23 
     | 
    
         
            +
              puts "Configuration file could not be found at #{config_path}"
         
     | 
| 
      
 24 
     | 
    
         
            +
              exit -1
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            # Read in JIRA options from jira.config.
         
     | 
| 
      
 28 
     | 
    
         
            +
            service_options = begin
         
     | 
| 
      
 29 
     | 
    
         
            +
              YAML.load_file(config_path)
         
     | 
| 
      
 30 
     | 
    
         
            +
            rescue ArgumentError => e
         
     | 
| 
      
 31 
     | 
    
         
            +
              raise "Could not parse YAML #{config_path} : #{e.message}"
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            log = NexposeTicketing::NxLogger.instance
         
     | 
| 
      
 35 
     | 
    
         
            +
            log.setup_statistics_collection(service_options["vendor"], 
         
     | 
| 
      
 36 
     | 
    
         
            +
                                            service_options["product"], 
         
     | 
| 
      
 37 
     | 
    
         
            +
                                            NexposeTicketing::VERSION)
         
     | 
| 
      
 38 
     | 
    
         
            +
            log.setup_logging(true, 'info')
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            current_encoding = Encoding.default_external=Encoding.find("UTF-8")
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            log.log_message("Current Encoding set to: #{current_encoding}")
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            # Initialize Ticket Service using JIRA.
         
     | 
| 
      
 45 
     | 
    
         
            +
            NexposeTicketing.start(service_options)
         
     | 
| 
         @@ -2,7 +2,11 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # This configuration file defines all the particular options necessary to support the helper.
         
     | 
| 
       3 
3 
     | 
    
         
             
            # Fields marked (M) are mandatory.
         
     | 
| 
       4 
4 
     | 
    
         
             
            #
         
     | 
| 
       5 
     | 
    
         
            -
            # (M) 
     | 
| 
      
 5 
     | 
    
         
            +
            # (M) Product name
         
     | 
| 
      
 6 
     | 
    
         
            +
            :product: JIRA
         
     | 
| 
      
 7 
     | 
    
         
            +
            # (M) Vendor
         
     | 
| 
      
 8 
     | 
    
         
            +
            :vendor: Atlassian
         
     | 
| 
      
 9 
     | 
    
         
            +
            # (M) Helper class name.
         
     | 
| 
       6 
10 
     | 
    
         
             
            :helper_name: JiraHelper
         
     | 
| 
       7 
11 
     | 
    
         
             
            # Optional parameters, these are implementation specific.
         
     | 
| 
       8 
12 
     | 
    
         
             
            :jira_url: https://url/rest/api/2/issue/
         
     | 
| 
         @@ -2,7 +2,10 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # This configuration file defines all the options necessary to support the helper.
         
     | 
| 
       3 
3 
     | 
    
         
             
            # Fields marked (M) are mandatory.
         
     | 
| 
       4 
4 
     | 
    
         
             
            #
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
            # (M) Product name
         
     | 
| 
      
 6 
     | 
    
         
            +
            :product: ServiceDesk
         
     | 
| 
      
 7 
     | 
    
         
            +
            # (M) Vendor
         
     | 
| 
      
 8 
     | 
    
         
            +
            :vendor: ManageEngine
         
     | 
| 
       6 
9 
     | 
    
         
             
            # (M) Helper class name
         
     | 
| 
       7 
10 
     | 
    
         
             
            :helper_name: ServiceDeskHelper
         
     | 
| 
       8 
11 
     | 
    
         | 
| 
         @@ -2,7 +2,10 @@ 
     | 
|
| 
       2 
2 
     | 
    
         
             
            # This configuration file defines all the options necessary to support the helper.
         
     | 
| 
       3 
3 
     | 
    
         
             
            # Fields marked (M) are mandatory.
         
     | 
| 
       4 
4 
     | 
    
         
             
            #
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
            # (M) Product name
         
     | 
| 
      
 6 
     | 
    
         
            +
            :product: ServiceNow
         
     | 
| 
      
 7 
     | 
    
         
            +
            # (M) Vendor
         
     | 
| 
      
 8 
     | 
    
         
            +
            :vendor: ServiceNow
         
     | 
| 
       6 
9 
     | 
    
         
             
            # (M) Helper class name
         
     | 
| 
       7 
10 
     | 
    
         
             
            :helper_name: ServiceNowHelper
         
     | 
| 
       8 
11 
     | 
    
         | 
| 
         @@ -8,20 +8,28 @@ 
     | 
|
| 
       8 
8 
     | 
    
         
             
              :logging_enabled: true
         
     | 
| 
       9 
9 
     | 
    
         
             
              # (M) Sets the log level threshold for output.
         
     | 
| 
       10 
10 
     | 
    
         
             
              :log_level: info
         
     | 
| 
      
 11 
     | 
    
         
            +
              # Enables logger output to console.
         
     | 
| 
      
 12 
     | 
    
         
            +
              :log_console: false
         
     | 
| 
       11 
13 
     | 
    
         
             
              # Filters the reports to specific sites one per line, leave empty for no site.
         
     | 
| 
       12 
14 
     | 
    
         
             
              :sites:
         
     | 
| 
       13 
15 
     | 
    
         
             
              - '1'
         
     | 
| 
       14 
16 
     | 
    
         
             
              # Minimum floor severity to report on. Number between 0 and 10.
         
     | 
| 
       15 
17 
     | 
    
         
             
              :severity: 8
         
     | 
| 
       16 
18 
     | 
    
         
             
              # (M) Name of the report historical file for sites saved in disk.
         
     | 
| 
       17 
     | 
    
         
            -
              : 
     | 
| 
      
 19 
     | 
    
         
            +
              :site_file_name: last_scan_data.csv
         
     | 
| 
       18 
20 
     | 
    
         
             
              # (M) Name of the report historical file for tags saved in disk.
         
     | 
| 
       19 
21 
     | 
    
         
             
              :tag_file_name: tag_last_scan_data.csv
         
     | 
| 
       20 
22 
     | 
    
         
             
              # (M) Defines the ticket creation mode:
         
     | 
| 
       21 
     | 
    
         
            -
              # ' 
     | 
| 
       22 
     | 
    
         
            -
              # ' 
     | 
| 
       23 
     | 
    
         
            -
              # ' 
     | 
| 
       24 
     | 
    
         
            -
              :ticket_mode:  
     | 
| 
      
 23 
     | 
    
         
            +
              # 'Default' Default IP *-* Vulnerability
         
     | 
| 
      
 24 
     | 
    
         
            +
              # 'IP' IP address -* Vulnerability
         
     | 
| 
      
 25 
     | 
    
         
            +
              # 'Vulnerability' Vulnerability -* IP Address
         
     | 
| 
      
 26 
     | 
    
         
            +
              :ticket_mode: IP
         
     | 
| 
      
 27 
     | 
    
         
            +
              # The maximum character length of a ticket description (-1 is unbounded)
         
     | 
| 
      
 28 
     | 
    
         
            +
              :max_ticket_length: -1
         
     | 
| 
      
 29 
     | 
    
         
            +
              # The maximum character length of a ticket title, including elipsis (...)
         
     | 
| 
      
 30 
     | 
    
         
            +
              :max_title_length: 100
         
     | 
| 
      
 31 
     | 
    
         
            +
              # The number of references to include in the ticket description
         
     | 
| 
      
 32 
     | 
    
         
            +
              :max_num_refs: 3
         
     | 
| 
       25 
33 
     | 
    
         
             
              # Timeout in seconds. The number of seconds the GEM waits for a response from Nexpose before exiting.
         
     | 
| 
       26 
34 
     | 
    
         
             
              :timeout: 10800
         
     | 
| 
       27 
35 
     | 
    
         
             
              # Ticket batching. Breaks ticket processing into groups of value size controlling resource utilisation of both systems.
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'net/http'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'net/https'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'uri'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'csv'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'nexpose_ticketing/nx_logger'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'nexpose_ticketing/version'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative '../ticket_metrics'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            class BaseHelper
         
     | 
| 
      
 11 
     | 
    
         
            +
              attr_accessor :service_data, :options
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def initialize(service_data, options, mode)
         
     | 
| 
      
 14 
     | 
    
         
            +
                @service_data = service_data
         
     | 
| 
      
 15 
     | 
    
         
            +
                @options = options
         
     | 
| 
      
 16 
     | 
    
         
            +
                @log = NexposeTicketing::NxLogger.instance
         
     | 
| 
      
 17 
     | 
    
         
            +
                @metrics = NexposeTicketing::TicketMetrics.new
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                load_dependencies
         
     | 
| 
      
 20 
     | 
    
         
            +
                @mode_helper = mode
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              # Load the mode helper specified in the config
         
     | 
| 
      
 24 
     | 
    
         
            +
              def load_dependencies  
         
     | 
| 
      
 25 
     | 
    
         
            +
                file = "#{@options[:ticket_mode]}_mode.rb".downcase
         
     | 
| 
      
 26 
     | 
    
         
            +
                path = File.join(File.dirname(__FILE__), "../modes/#{file}")
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                @log.log_message("Loading #{@options[:ticket_mode]} mode dependencies.")
         
     | 
| 
      
 29 
     | 
    
         
            +
                begin 
         
     | 
| 
      
 30 
     | 
    
         
            +
                  require_relative path
         
     | 
| 
      
 31 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 32 
     | 
    
         
            +
                  error = "Ticket mode dependency '#{file}' could not be loaded."
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @log.log_error_message e.to_s
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @log.log_error_message error
         
     | 
| 
      
 35 
     | 
    
         
            +
                  fail error
         
     | 
| 
      
 36 
     | 
    
         
            +
                end 
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              # Performs any necessary clean-up
         
     | 
| 
      
 40 
     | 
    
         
            +
              def finish
         
     | 
| 
      
 41 
     | 
    
         
            +
                @metrics.finish
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -5,20 +5,15 @@ require 'uri' 
     | 
|
| 
       5 
5 
     | 
    
         
             
            require 'csv'
         
     | 
| 
       6 
6 
     | 
    
         
             
            require 'nexpose_ticketing/nx_logger'
         
     | 
| 
       7 
7 
     | 
    
         
             
            require 'nexpose_ticketing/version'
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative './base_helper'
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            # This class serves as the JIRA interface
         
     | 
| 
       11 
11 
     | 
    
         
             
            # that creates issues within JIRA from vulnerabilities
         
     | 
| 
       12 
12 
     | 
    
         
             
            # found in Nexpose.
         
     | 
| 
       13 
13 
     | 
    
         
             
            # Copyright:: Copyright (c) 2014 Rapid7, LLC.
         
     | 
| 
       14 
     | 
    
         
            -
            class JiraHelper
         
     | 
| 
       15 
     | 
    
         
            -
               
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                @jira_data = jira_data
         
     | 
| 
       18 
     | 
    
         
            -
                @options = options
         
     | 
| 
       19 
     | 
    
         
            -
                @log = NexposeTicketing::NxLogger.instance
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                @common_helper = NexposeTicketing::CommonHelper.new(@options)
         
     | 
| 
      
 14 
     | 
    
         
            +
            class JiraHelper < BaseHelper
         
     | 
| 
      
 15 
     | 
    
         
            +
              def initialize(service_data, options, mode)
         
     | 
| 
      
 16 
     | 
    
         
            +
                super(service_data, options, mode)
         
     | 
| 
       22 
17 
     | 
    
         
             
              end
         
     | 
| 
       23 
18 
     | 
    
         | 
| 
       24 
19 
     | 
    
         
             
              # Fetches the Jira ticket key e.g INT-1. This is required to post updates to the Jira.
         
     | 
| 
         @@ -29,24 +24,34 @@ class JiraHelper 
     | 
|
| 
       29 
24 
     | 
    
         
             
              # * *Returns* :
         
     | 
| 
       30 
25 
     | 
    
         
             
              #   - Jira ticket key if found, nil otherwise.
         
     | 
| 
       31 
26 
     | 
    
         
             
              #
         
     | 
| 
       32 
     | 
    
         
            -
              def get_jira_key(jql_query)
         
     | 
| 
      
 27 
     | 
    
         
            +
              def get_jira_key(jql_query, nxid = nil)
         
     | 
| 
       33 
28 
     | 
    
         
             
                fail 'JQL query string cannot be empty.' if jql_query.empty?
         
     | 
| 
       34 
29 
     | 
    
         
             
                headers = { 'Content-Type' => 'application/json',
         
     | 
| 
       35 
30 
     | 
    
         
             
                            'Accept' => 'application/json' }
         
     | 
| 
       36 
31 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                uri = URI.parse(("#{@ 
     | 
| 
      
 32 
     | 
    
         
            +
                uri = URI.parse(("#{@service_data[:jira_url]}".split("/")[0..-2].join('/') + '/search'))
         
     | 
| 
       38 
33 
     | 
    
         
             
                uri.query = [uri.query, URI.escape(jql_query)].compact.join('&')
         
     | 
| 
       39 
34 
     | 
    
         
             
                req = Net::HTTP::Get.new(uri.to_s, headers)
         
     | 
| 
       40 
35 
     | 
    
         
             
                response = send_jira_request(uri, req)
         
     | 
| 
       41 
36 
     | 
    
         | 
| 
       42 
37 
     | 
    
         
             
                issues = JSON.parse(response.body)['issues']
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                if issues.nil? || !issues.any?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @log.log_message "JIRA did not return any keys for query containing NXID #{nxid}"
         
     | 
| 
      
 41 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                if issues.size > 1
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # If Jira returns more than one key for a "unique" NXID query result then something has gone wrong...
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # Safest response is to return no key and let logic elsewhere dictate the action to take.
         
     | 
| 
      
 47 
     | 
    
         
            +
                  error = "Jira returned multiple keys for query containing NXID #{nxid}."
         
     | 
| 
      
 48 
     | 
    
         
            +
                  error += " Please check project within JIRA."
         
     | 
| 
      
 49 
     | 
    
         
            +
                  error += " Response was <#{issues}>"
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @log.log_error_message(error)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
               issues[0]['key']
         
     | 
| 
       50 
55 
     | 
    
         
             
              end
         
     | 
| 
       51 
56 
     | 
    
         | 
| 
       52 
57 
     | 
    
         
             
              # Sends a HTTP request to the JIRA console. 
         
     | 
| 
         @@ -59,7 +64,7 @@ class JiraHelper 
     | 
|
| 
       59 
64 
     | 
    
         
             
              #   - HTTPResponse containing result from the JIRA console.
         
     | 
| 
       60 
65 
     | 
    
         
             
              #
         
     | 
| 
       61 
66 
     | 
    
         
             
              def send_request(uri, request, ticket=false)
         
     | 
| 
       62 
     | 
    
         
            -
                request.basic_auth @ 
     | 
| 
      
 67 
     | 
    
         
            +
                request.basic_auth @service_data[:username], @service_data[:password]
         
     | 
| 
       63 
68 
     | 
    
         
             
                resp = Net::HTTP.new(uri.host, uri.port)
         
     | 
| 
       64 
69 
     | 
    
         | 
| 
       65 
70 
     | 
    
         
             
                # Enable this line for debugging the https call.
         
     | 
| 
         @@ -72,9 +77,39 @@ class JiraHelper 
     | 
|
| 
       72 
77 
     | 
    
         | 
| 
       73 
78 
     | 
    
         
             
                resp.start do |http|
         
     | 
| 
       74 
79 
     | 
    
         
             
                  res = http.request(request)
         
     | 
| 
       75 
     | 
    
         
            -
                   
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
                   
     | 
| 
      
 80 
     | 
    
         
            +
                  code = res.code.to_i
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  next if code.between?(200,299)
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                  unless code.between?(400, 499)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @log.log_error_message("Error submitting ticket data: #{res.message}, #{res.body}")
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return res
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  @log.log_error_message("Unable to access JIRA.")
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @log.log_error_message "Error code: #{code}"
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  #Bad project etc
         
     | 
| 
      
 93 
     | 
    
         
            +
                  case code
         
     | 
| 
      
 94 
     | 
    
         
            +
                  when 400
         
     | 
| 
      
 95 
     | 
    
         
            +
                    errors = res.body.scan(/errors":{(.+)}}/).first.first
         
     | 
| 
      
 96 
     | 
    
         
            +
                    errors = errors.gsub('"', '').gsub(':', ': ').gsub(',', "\n")
         
     | 
| 
      
 97 
     | 
    
         
            +
                    @log.log_error_message "Error messages:\n#{errors}"
         
     | 
| 
      
 98 
     | 
    
         
            +
                  #Log in failed
         
     | 
| 
      
 99 
     | 
    
         
            +
                  when 401
         
     | 
| 
      
 100 
     | 
    
         
            +
                    @log.log_error_message "Message: #{res.message.strip}"
         
     | 
| 
      
 101 
     | 
    
         
            +
                    @log.log_error_message "Reason: #{res['x-seraph-loginreason']}"
         
     | 
| 
      
 102 
     | 
    
         
            +
                  #Locked out
         
     | 
| 
      
 103 
     | 
    
         
            +
                  when 403
         
     | 
| 
      
 104 
     | 
    
         
            +
                    @log.log_error_message "Message: #{res.message.strip}"
         
     | 
| 
      
 105 
     | 
    
         
            +
                    @log.log_error_message "Reason: #{res['x-seraph-loginreason']}"
         
     | 
| 
      
 106 
     | 
    
         
            +
                    @log.log_error_message "#{res['x-authentication-denied-reason']}"
         
     | 
| 
      
 107 
     | 
    
         
            +
                  else
         
     | 
| 
      
 108 
     | 
    
         
            +
                    #e.g. 404 - bad URL
         
     | 
| 
      
 109 
     | 
    
         
            +
                    @log.log_error_message "Message: #{res.message.strip}"
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                  return res
         
     | 
| 
       78 
113 
     | 
    
         
             
                end
         
     | 
| 
       79 
114 
     | 
    
         
             
              end
         
     | 
| 
       80 
115 
     | 
    
         | 
| 
         @@ -120,7 +155,7 @@ class JiraHelper 
     | 
|
| 
       120 
155 
     | 
    
         
             
                headers = { 'Content-Type' => 'application/json',
         
     | 
| 
       121 
156 
     | 
    
         
             
                            'Accept' => 'application/json' }
         
     | 
| 
       122 
157 
     | 
    
         | 
| 
       123 
     | 
    
         
            -
                uri = URI.parse(("#{@ 
     | 
| 
      
 158 
     | 
    
         
            +
                uri = URI.parse(("#{@service_data[:jira_url]}#{jira_key}/transitions?expand=transitions.fields."))
         
     | 
| 
       124 
159 
     | 
    
         
             
                req = Net::HTTP::Get.new(uri.to_s, headers)
         
     | 
| 
       125 
160 
     | 
    
         
             
                response = send_jira_request(uri, req)
         
     | 
| 
       126 
161 
     | 
    
         | 
| 
         @@ -133,42 +168,39 @@ class JiraHelper 
     | 
|
| 
       133 
168 
     | 
    
         
             
                    end
         
     | 
| 
       134 
169 
     | 
    
         
             
                  end
         
     | 
| 
       135 
170 
     | 
    
         
             
                end
         
     | 
| 
       136 
     | 
    
         
            -
                error = "Response was <#{transitions}> and desired close Step ID was <#{@ 
     | 
| 
      
 171 
     | 
    
         
            +
                error = "Response was <#{transitions}> and desired close Step ID was <#{@service_data[:close_step_id]}>. Jira returned no valid transition to close the ticket!"
         
     | 
| 
       137 
172 
     | 
    
         
             
                @log.log_message(error)
         
     | 
| 
       138 
173 
     | 
    
         
             
                return nil
         
     | 
| 
       139 
174 
     | 
    
         
             
              end
         
     | 
| 
       140 
175 
     | 
    
         | 
| 
       141 
176 
     | 
    
         
             
              def create_tickets(tickets)
         
     | 
| 
       142 
177 
     | 
    
         
             
                fail 'Ticket(s) cannot be empty.' if tickets.nil? || tickets.empty?
         
     | 
| 
      
 178 
     | 
    
         
            +
                created_tickets = 0
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
       143 
180 
     | 
    
         
             
                tickets.each do |ticket|
         
     | 
| 
       144 
181 
     | 
    
         
             
                  headers = { 'Content-Type' => 'application/json',
         
     | 
| 
       145 
182 
     | 
    
         
             
                              'Accept' => 'application/json' }
         
     | 
| 
       146 
183 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
                  uri = URI.parse("#{@ 
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
      
 184 
     | 
    
         
            +
                  uri = URI.parse("#{@service_data[:jira_url]}")
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                  req = Net::HTTP::Post.new(uri, headers)
         
     | 
| 
       149 
187 
     | 
    
         
             
                  req.body = ticket
         
     | 
| 
       150 
     | 
    
         
            -
                   
     | 
| 
      
 188 
     | 
    
         
            +
                  
         
     | 
| 
      
 189 
     | 
    
         
            +
                  code = send_ticket(uri, req).code.to_i
         
     | 
| 
      
 190 
     | 
    
         
            +
                  break if code.between?(400, 499)
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                  created_tickets += 1
         
     | 
| 
       151 
193 
     | 
    
         
             
                end
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                @metrics.created created_tickets
         
     | 
| 
       152 
196 
     | 
    
         
             
              end
         
     | 
| 
       153 
197 
     | 
    
         | 
| 
       154 
198 
     | 
    
         
             
              # Prepares tickets from the CSV.
         
     | 
| 
       155 
199 
     | 
    
         
             
              def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
         
     | 
| 
      
 200 
     | 
    
         
            +
                @metrics.start
         
     | 
| 
       156 
201 
     | 
    
         
             
                @log.log_message('Preparing ticket requests...')
         
     | 
| 
       157 
     | 
    
         
            -
                 
     | 
| 
       158 
     | 
    
         
            -
                 
     | 
| 
       159 
     | 
    
         
            -
                when 'D' then matching_fields = ['ip_address', 'vulnerability_id']
         
     | 
| 
       160 
     | 
    
         
            -
                # 'I' IP address -* Vulnerability
         
     | 
| 
       161 
     | 
    
         
            -
                when 'I' then matching_fields = ['ip_address']
         
     | 
| 
       162 
     | 
    
         
            -
                # 'V' Vulnerability -* Assets
         
     | 
| 
       163 
     | 
    
         
            -
                when 'V' then matching_fields = ['vulnerability_id']
         
     | 
| 
       164 
     | 
    
         
            -
                else
         
     | 
| 
       165 
     | 
    
         
            -
                    fail 'Unsupported ticketing mode selected.'
         
     | 
| 
       166 
     | 
    
         
            -
                end
         
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
         
     | 
| 
       169 
     | 
    
         
            -
              end
         
     | 
| 
       170 
     | 
    
         
            -
             
     | 
| 
       171 
     | 
    
         
            -
              def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
         
     | 
| 
      
 202 
     | 
    
         
            +
                matching_fields = @mode_helper.get_matching_fields
         
     | 
| 
      
 203 
     | 
    
         
            +
                
         
     | 
| 
       172 
204 
     | 
    
         
             
                @ticket = Hash.new(-1)
         
     | 
| 
       173 
205 
     | 
    
         | 
| 
       174 
206 
     | 
    
         
             
                @log.log_message("Preparing tickets for #{@options[:ticket_mode]} mode.")
         
     | 
| 
         @@ -182,34 +214,35 @@ class JiraHelper 
     | 
|
| 
       182 
214 
     | 
    
         
             
                    @ticket = {
         
     | 
| 
       183 
215 
     | 
    
         
             
                        'fields' => {
         
     | 
| 
       184 
216 
     | 
    
         
             
                            'project' => {
         
     | 
| 
       185 
     | 
    
         
            -
                                'key' => "#{@ 
     | 
| 
       186 
     | 
    
         
            -
                            'summary' => @ 
     | 
| 
      
 217 
     | 
    
         
            +
                                'key' => "#{@service_data[:project]}" },
         
     | 
| 
      
 218 
     | 
    
         
            +
                            'summary' => @mode_helper.get_title(row),
         
     | 
| 
       187 
219 
     | 
    
         
             
                            'description' => '',
         
     | 
| 
       188 
220 
     | 
    
         
             
                            'issuetype' => {
         
     | 
| 
       189 
221 
     | 
    
         
             
                                'name' => 'Task' }
         
     | 
| 
       190 
222 
     | 
    
         
             
                        }
         
     | 
| 
       191 
223 
     | 
    
         
             
                    }
         
     | 
| 
       192 
     | 
    
         
            -
                    description = @ 
     | 
| 
      
 224 
     | 
    
         
            +
                    description = @mode_helper.get_description(nexpose_identifier_id, row)
         
     | 
| 
       193 
225 
     | 
    
         
             
                  elsif matching_fields.any? { |x| previous_row[x].nil? || previous_row[x] != row[x] }
         
     | 
| 
       194 
     | 
    
         
            -
                    info = @ 
     | 
| 
      
 226 
     | 
    
         
            +
                    info = @mode_helper.get_field_info(matching_fields, previous_row)
         
     | 
| 
       195 
227 
     | 
    
         
             
                    @log.log_message("Generated ticket with #{info}")
         
     | 
| 
       196 
228 
     | 
    
         | 
| 
       197 
     | 
    
         
            -
                    @ticket['fields']['description'] = @ 
     | 
| 
      
 229 
     | 
    
         
            +
                    @ticket['fields']['description'] = @mode_helper.print_description(description)
         
     | 
| 
       198 
230 
     | 
    
         
             
                    tickets.push(@ticket.to_json)
         
     | 
| 
       199 
231 
     | 
    
         
             
                    previous_row = nil
         
     | 
| 
       200 
232 
     | 
    
         
             
                    description = nil
         
     | 
| 
       201 
233 
     | 
    
         
             
                    redo
         
     | 
| 
       202 
234 
     | 
    
         
             
                  else
         
     | 
| 
       203 
     | 
    
         
            -
                    description = @ 
     | 
| 
      
 235 
     | 
    
         
            +
                    description = @mode_helper.update_description(description, row)
         
     | 
| 
       204 
236 
     | 
    
         
             
                  end
         
     | 
| 
       205 
237 
     | 
    
         
             
                end
         
     | 
| 
       206 
238 
     | 
    
         | 
| 
       207 
239 
     | 
    
         
             
                unless @ticket.nil? || @ticket.empty?
         
     | 
| 
       208 
     | 
    
         
            -
                   
     | 
| 
      
 240 
     | 
    
         
            +
                  info = @mode_helper.get_field_info(matching_fields, previous_row)
         
     | 
| 
      
 241 
     | 
    
         
            +
                  @log.log_message("Generated ticket with #{info}")
         
     | 
| 
      
 242 
     | 
    
         
            +
                  @ticket['fields']['description'] = @mode_helper.print_description(description)
         
     | 
| 
       209 
243 
     | 
    
         
             
                  tickets.push(@ticket.to_json)
         
     | 
| 
       210 
244 
     | 
    
         
             
                end
         
     | 
| 
       211 
245 
     | 
    
         | 
| 
       212 
     | 
    
         
            -
                @log.log_message("Generated <#{tickets.count.to_s}> tickets.")
         
     | 
| 
       213 
246 
     | 
    
         
             
                tickets
         
     | 
| 
       214 
247 
     | 
    
         
             
              end
         
     | 
| 
       215 
248 
     | 
    
         | 
| 
         @@ -222,43 +255,48 @@ class JiraHelper 
     | 
|
| 
       222 
255 
     | 
    
         
             
              def close_tickets(tickets)
         
     | 
| 
       223 
256 
     | 
    
         
             
                if tickets.nil? || tickets.empty?
         
     | 
| 
       224 
257 
     | 
    
         
             
                  @log.log_message('No tickets to close.')
         
     | 
| 
       225 
     | 
    
         
            -
             
     | 
| 
       226 
     | 
    
         
            -
             
     | 
| 
       227 
     | 
    
         
            -
             
     | 
| 
      
 258 
     | 
    
         
            +
                  return
         
     | 
| 
      
 259 
     | 
    
         
            +
                end
         
     | 
| 
      
 260 
     | 
    
         
            +
                closed_count = 0
         
     | 
| 
       228 
261 
     | 
    
         | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
       230 
     | 
    
         
            -
             
     | 
| 
       231 
     | 
    
         
            -
                    req = Net::HTTP::Post.new(uri.to_s, headers)
         
     | 
| 
      
 262 
     | 
    
         
            +
                headers = { 'Content-Type' => 'application/json',
         
     | 
| 
      
 263 
     | 
    
         
            +
                            'Accept' => 'application/json' }
         
     | 
| 
       232 
264 
     | 
    
         | 
| 
       233 
     | 
    
         
            -
             
     | 
| 
       234 
     | 
    
         
            -
             
     | 
| 
       235 
     | 
    
         
            -
             
     | 
| 
       236 
     | 
    
         
            -
                      @log.log_message("No valid transition found for ticket <#{ticket}>. Skipping closure.")
         
     | 
| 
       237 
     | 
    
         
            -
                      next
         
     | 
| 
       238 
     | 
    
         
            -
                    end
         
     | 
| 
      
 265 
     | 
    
         
            +
                tickets.each do |ticket|
         
     | 
| 
      
 266 
     | 
    
         
            +
                  uri = URI.parse(("#{@service_data[:jira_url]}#{ticket}/transitions"))
         
     | 
| 
      
 267 
     | 
    
         
            +
                  req = Net::HTTP::Post.new(uri.to_s, headers)
         
     | 
| 
       239 
268 
     | 
    
         | 
| 
       240 
     | 
    
         
            -
             
     | 
| 
       241 
     | 
    
         
            -
             
     | 
| 
       242 
     | 
    
         
            -
                    transition 
     | 
| 
       243 
     | 
    
         
            -
             
     | 
| 
       244 
     | 
    
         
            -
             
     | 
| 
       245 
     | 
    
         
            -
             
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
     | 
    
         
            -
             
     | 
| 
       248 
     | 
    
         
            -
             
     | 
| 
       249 
     | 
    
         
            -
             
     | 
| 
       250 
     | 
    
         
            -
             
     | 
| 
       251 
     | 
    
         
            -
             
     | 
| 
       252 
     | 
    
         
            -
             
     | 
| 
       253 
     | 
    
         
            -
             
     | 
| 
       254 
     | 
    
         
            -
             
     | 
| 
       255 
     | 
    
         
            -
             
     | 
| 
      
 269 
     | 
    
         
            +
                  transition = get_jira_transition_details(ticket, @service_data[:close_step_id])
         
     | 
| 
      
 270 
     | 
    
         
            +
                  if transition.nil?
         
     | 
| 
      
 271 
     | 
    
         
            +
                    #Valid transition could not be found. Ignore ticket since we do not know what to do with it.
         
     | 
| 
      
 272 
     | 
    
         
            +
                    @log.log_message("No valid transition found for ticket <#{ticket}>. Skipping closure.")
         
     | 
| 
      
 273 
     | 
    
         
            +
                    next
         
     | 
| 
      
 274 
     | 
    
         
            +
                  end
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                  #We need to find any required fields to send with the transition request
         
     | 
| 
      
 277 
     | 
    
         
            +
                  required_fields = []
         
     | 
| 
      
 278 
     | 
    
         
            +
                  transition['fields'].each do |field|
         
     | 
| 
      
 279 
     | 
    
         
            +
                    next unless field[1]['required'] == true
         
     | 
| 
      
 280 
     | 
    
         
            +
                      
         
     | 
| 
      
 281 
     | 
    
         
            +
                    # Currently only required fields with 'allowedValues' in the JSON response are supported.
         
     | 
| 
      
 282 
     | 
    
         
            +
                    if not field[1].has_key? 'allowedValues'
         
     | 
| 
      
 283 
     | 
    
         
            +
                      @log.log_message("Closing ticket <#{ticket}> requires a field I know nothing about! Transition details are <#{transition}>. Ignoring this field.")
         
     | 
| 
      
 284 
     | 
    
         
            +
                        next
         
     | 
| 
       256 
285 
     | 
    
         
             
                    end
         
     | 
| 
       257 
286 
     | 
    
         | 
| 
       258 
     | 
    
         
            -
                     
     | 
| 
       259 
     | 
    
         
            -
                     
     | 
| 
      
 287 
     | 
    
         
            +
                    field = "{\"id\" : \"#{field[1]['allowedValues'][0]['id']}\"}"
         
     | 
| 
      
 288 
     | 
    
         
            +
                    field = "[#{field}]" if field[1]['schema']['type'] == 'array'
         
     | 
| 
      
 289 
     | 
    
         
            +
                    required_fields << "\"#{field[0]}\" : #{field}"
         
     | 
| 
       260 
290 
     | 
    
         
             
                  end
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
                  req.body = "{\"transition\" : {\"id\" : #{transition['id']}}, \"fields\" : { #{required_fields.join(",")}}}"
         
     | 
| 
      
 293 
     | 
    
         
            +
                  code = send_ticket(uri, req).code.to_i
         
     | 
| 
      
 294 
     | 
    
         
            +
                  break if code.between?(400, 499)
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
                  closed_count += 1
         
     | 
| 
       261 
297 
     | 
    
         
             
                end
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
                @metrics.closed closed_count
         
     | 
| 
       262 
300 
     | 
    
         
             
              end
         
     | 
| 
       263 
301 
     | 
    
         | 
| 
       264 
302 
     | 
    
         
             
              # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.
         
     | 
| 
         @@ -274,9 +312,10 @@ class JiraHelper 
     | 
|
| 
       274 
312 
     | 
    
         
             
                @nxid = nil
         
     | 
| 
       275 
313 
     | 
    
         
             
                tickets = []
         
     | 
| 
       276 
314 
     | 
    
         
             
                CSV.parse(vulnerability_list.chomp, headers: :first_row)  do |row|
         
     | 
| 
       277 
     | 
    
         
            -
                  @nxid = @ 
     | 
| 
      
 315 
     | 
    
         
            +
                  @nxid = @mode_helper.get_nxid(nexpose_identifier_id, row)
         
     | 
| 
       278 
316 
     | 
    
         
             
                  # Query Jira for the ticket by unique id (generated NXID)
         
     | 
| 
       279 
     | 
    
         
            -
                   
     | 
| 
      
 317 
     | 
    
         
            +
                  query_string = "jql=project=#{@service_data[:project]} AND description ~ \"NXID: #{@nxid}\" AND (status != #{@service_data[:close_step_name]})&fields=key"
         
     | 
| 
      
 318 
     | 
    
         
            +
                  queried_key = get_jira_key(query_string, @nxid)
         
     | 
| 
       280 
319 
     | 
    
         
             
                  if queried_key.nil? || queried_key.empty?
         
     | 
| 
       281 
320 
     | 
    
         
             
                    @log.log_message("Error when closing tickets - query for NXID <#{@nxid}> should have returned a Jira key!!")
         
     | 
| 
       282 
321 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -284,6 +323,7 @@ class JiraHelper 
     | 
|
| 
       284 
323 
     | 
    
         
             
                    tickets.push(queried_key)
         
     | 
| 
       285 
324 
     | 
    
         
             
                  end
         
     | 
| 
       286 
325 
     | 
    
         
             
                end
         
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
       287 
327 
     | 
    
         
             
                tickets
         
     | 
| 
       288 
328 
     | 
    
         
             
              end
         
     | 
| 
       289 
329 
     | 
    
         | 
| 
         @@ -300,20 +340,28 @@ class JiraHelper 
     | 
|
| 
       300 
340 
     | 
    
         
             
                  tickets.each do |ticket_details|
         
     | 
| 
       301 
341 
     | 
    
         
             
                    headers = {'Content-Type' => 'application/json',
         
     | 
| 
       302 
342 
     | 
    
         
             
                               'Accept' => 'application/json'}
         
     | 
| 
      
 343 
     | 
    
         
            +
                  
         
     | 
| 
      
 344 
     | 
    
         
            +
                    create_new_ticket = ticket_details.first.nil?
         
     | 
| 
      
 345 
     | 
    
         
            +
             
     | 
| 
      
 346 
     | 
    
         
            +
                    url = "#{service_data[:jira_url]}"
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                    if create_new_ticket
         
     | 
| 
      
 349 
     | 
    
         
            +
                      req = Net::HTTP::Post.new(url, headers)
         
     | 
| 
      
 350 
     | 
    
         
            +
                      req.body = ticket_details.last
         
     | 
| 
      
 351 
     | 
    
         
            +
                    else
         
     | 
| 
      
 352 
     | 
    
         
            +
                      url += "#{ticket_details[0]}"
         
     | 
| 
      
 353 
     | 
    
         
            +
                      req = Net::HTTP::Put.new(url, headers)
         
     | 
| 
      
 354 
     | 
    
         
            +
                      req.body = {'update' => {'description' => [{'set' => "#{JSON.parse(ticket_details[1])['fields']['description']}"}]}}.to_json
         
     | 
| 
      
 355 
     | 
    
         
            +
                    end
         
     | 
| 
       303 
356 
     | 
    
         | 
| 
       304 
     | 
    
         
            -
                    ( 
     | 
| 
       305 
     | 
    
         
            -
             
     | 
| 
       306 
     | 
    
         
            -
             
     | 
| 
       307 
     | 
    
         
            -
                     
     | 
| 
       308 
     | 
    
         
            -
             
     | 
| 
       309 
     | 
    
         
            -
             
     | 
| 
       310 
     | 
    
         
            -
             
     | 
| 
       311 
     | 
    
         
            -
             
     | 
| 
       312 
     | 
    
         
            -
                    send_whole_ticket ?
         
     | 
| 
       313 
     | 
    
         
            -
                        req.body = ticket_details.last :
         
     | 
| 
       314 
     | 
    
         
            -
                        req.body = {'update' => {'description' => [{'set' => "#{JSON.parse(ticket_details[1])['fields']['description']}"}]}}.to_json
         
     | 
| 
       315 
     | 
    
         
            -
             
     | 
| 
       316 
     | 
    
         
            -
                    send_ticket(uri, req)
         
     | 
| 
      
 357 
     | 
    
         
            +
                    code = send_ticket(URI.parse(url), req).code.to_i
         
     | 
| 
      
 358 
     | 
    
         
            +
                    break if code.between?(400, 499)
         
     | 
| 
      
 359 
     | 
    
         
            +
                     
         
     | 
| 
      
 360 
     | 
    
         
            +
                    if create_new_ticket
         
     | 
| 
      
 361 
     | 
    
         
            +
                      @metrics.created
         
     | 
| 
      
 362 
     | 
    
         
            +
                    else
         
     | 
| 
      
 363 
     | 
    
         
            +
                      @metrics.updated
         
     | 
| 
      
 364 
     | 
    
         
            +
                    end
         
     | 
| 
       317 
365 
     | 
    
         
             
                  end
         
     | 
| 
       318 
366 
     | 
    
         
             
                end
         
     | 
| 
       319 
367 
     | 
    
         
             
              end
         
     | 
| 
         @@ -327,7 +375,9 @@ class JiraHelper 
     | 
|
| 
       327 
375 
     | 
    
         
             
              #   - List of JSON-formated tickets for updating within Jira.
         
     | 
| 
       328 
376 
     | 
    
         
             
              #
         
     | 
| 
       329 
377 
     | 
    
         
             
              def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
         
     | 
| 
       330 
     | 
    
         
            -
                 
     | 
| 
      
 378 
     | 
    
         
            +
                @metrics.start
         
     | 
| 
      
 379 
     | 
    
         
            +
                return unless @mode_helper.updates_supported?
         
     | 
| 
      
 380 
     | 
    
         
            +
                
         
     | 
| 
       331 
381 
     | 
    
         
             
                @log.log_message('Preparing tickets to update.')
         
     | 
| 
       332 
382 
     | 
    
         
             
                #Jira uses the ticket key to push updates. Since new IPs won't have a Jira key, generate new tickets for all of the IPs found.
         
     | 
| 
       333 
383 
     | 
    
         
             
                updated_tickets = prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
         
     | 
| 
         @@ -345,7 +395,9 @@ class JiraHelper 
     | 
|
| 
       345 
395 
     | 
    
         
             
                    @log.log_message("Failed to parse the NXID from a generated ticket update! Ignoring ticket <#{nxid}>")
         
     | 
| 
       346 
396 
     | 
    
         
             
                    next
         
     | 
| 
       347 
397 
     | 
    
         
             
                  end
         
     | 
| 
       348 
     | 
    
         
            -
             
     | 
| 
      
 398 
     | 
    
         
            +
             
     | 
| 
      
 399 
     | 
    
         
            +
                  query_string = "jql=project=#{@service_data[:project]} AND description ~ \"#{nxid.strip}\" AND (status != #{@service_data[:close_step_name]})&fields=key"
         
     | 
| 
      
 400 
     | 
    
         
            +
                  queried_key = get_jira_key(query_string, nxid)
         
     | 
| 
       349 
401 
     | 
    
         
             
                  ticket_key_pair = []
         
     | 
| 
       350 
402 
     | 
    
         
             
                  ticket_key_pair << queried_key
         
     | 
| 
       351 
403 
     | 
    
         
             
                  ticket_key_pair << ticket
         
     |