module Nexpose # This class represents each of the /NexposeReport[@version='1.0']/VulnerabilityDefinitions/vulnerability # elements in the Nexpose Full XML document. # # It provides a convenient way to access the information scattered all over # the XML in attributes and nested tags. # # Instead of providing separate methods for each supported property we rely # on Ruby's #method_missing to do most of the work. class Vulnerability # Accepts an XML node from Nokogiri::XML. def initialize(xml_node) @xml = xml_node end # List of supported tags. They can be attributes, simple descendans or # collections (e.g. , ) def supported_tags [ # attributes :nexpose_id, :title, :severity, :pci_severity, :cvss_score, :cvss_vector, :published, :added, :modified, # simple tags :description, :solution, # multiple tags :references, :tags ] end # This allows external callers (and specs) to check for implemented # properties def respond_to?(method, include_private=false) return true if supported_tags.include?(method.to_sym) super end # This method is invoked by Ruby when a method that is not defined in this # instance is called. # # In our case we inspect the @method@ parameter and try to find the # attribute, simple descendent or collection that it maps to in the XML # tree. def method_missing(method, *args) # We could remove this check and return nil for any non-recognized tag. # The problem would be that it would make tricky to debug problems with # typos. For instance: <>.potr would return nil instead of raising an # exception unless supported_tags.include?(method) super return end # First we try the attributes. In Ruby we use snake_case, but in XML # CamelCase is used for some attributes translations_table = { :nexpose_id => 'id', :pci_severity => 'pciSeverity', :cvss_score => 'cvssScore', :cvss_vector =>'cvssVector' } method_name = translations_table.fetch(method, method.to_s) return @xml.attributes[method_name].value if @xml.attributes.key?(method_name) # Then we try simple children tags: description, solution tag = @xml.xpath("./#{method_name}/ContainerBlockElement").first if tag lines = [] # Go through Paragraphs and extract them. # FIXME: we're using .//. to get paragraphs nested in Nexpose lists, # ideally we'd convert this lists into Textile bullet point lists. tag.xpath(".//Paragraph").each do |xml_paragraph| lines << xml_paragraph.text.split("\n").collect(&:strip).join(' ').strip end return lines.join("\n\n") end # Finally the enumerations: references, tags if method_name == 'references' @xml.xpath("./references/reference").collect{|entry| {:source => entry['source'], :text => entry.text} } elsif method == 'tags' @xml.xpath("./tags/tag").collect(&:text) else # nothing found, the tag is valid but not present in this ReportItem return nil end end end end