require_relative 'error' # Helps to validate xml against schemas contained in a WSDL class WsdlValidator # [String] Schemas found within WSDL attr_accessor :show_schemas # [Nokogiri::XML::Schema] attr_accessor :schemas attr_accessor :basic_auth attr_accessor :doc # Parse WSDL storing authentication and all schemas from it # @param [String] wsdl_url URL to where WSDL is stored or location to where file is stored def initialize(wsdl_url) self.basic_auth = extract_auth(wsdl_url) self.doc = Wasabi.document wsdl_url self.show_schemas = parse_wsdl_schemas self.schemas = Nokogiri::XML::Schema(show_schemas) rescue Wasabi::Resolver::HTTPError => resolver raise WsdlValidator::Error, "Unauthorized for basic auth #{basic_auth}" if resolver.response.code == 401 raise resolver rescue Nokogiri::XML::SyntaxError => syntax_error puts "Error for " + show_schemas raise syntax_error end # Gets the namespaces from the SOAP Envelope & Body element, adds them to the root element underneath the body # and returns that element. # @note This is not the ideal approach. Ideally Nokogiri parser would be able to understand SOAP xsd as well # @return [Nokogiri::XML::Document] Retrieve root element from SOAP def extract_root_from_soap(envelope) body = envelope.children.find { |child| child.name == 'Body' } root_element = body.children.reject { |child| child.is_a?(Nokogiri::XML::Text) }.first envelope.namespaces.each { |namespace, value| root_element[namespace] = value } body.namespaces.each { |namespace, value| root_element[namespace] = value } Nokogiri::XML root_element.to_s # Convert to Xml Document end # Returns a list of syntax errors. Empty list indicates valid xml # @param [String, Nokogiri::XML::NodeSet] xml # @return [Array] List of Nokogiri::XML::SyntaxError objects def errors_for(xml) raise "Incorrect type #{xml.class}" unless [String, Nokogiri::XML::Document, Nokogiri::XML::NodeSet].include? xml.class xml_under_test = Nokogiri::XML(xml.to_s) soap_envelope = xml_under_test.children.find { |e| e.name == 'Envelope' } xml_under_test = extract_root_from_soap(soap_envelope) if soap_envelope schemas.validate(xml_under_test) end # @param [String, Nokogiri::XML::NodeSet] xml # @return [Boolean, Exception] Whether xml is valid according to WSDL of class def valid?(xml) validator = errors_for xml raise WsdlValidator::Error, validator.join unless validator.empty? true end alias validate valid? private # Returns an Array with basic auth in it # @return [Array] Format is [user_name, password] or [] def extract_auth(wsdl_location) return [] unless wsdl_location.to_s =~ /^http|socks/ url = wsdl_location.is_a?(URI) ? wsdl_location : URI(wsdl_location) if url.user [url.user, url.password] else [] end end # Join all the schemas within the WSDL, importing schemas if necessary # @return [String] Schema XML contained within WSDL def parse_wsdl_schemas doc.parser.schemas.collect do |schema| imports = schema.children.select { |child| child.name == 'import' } imported_xsds = imports.collect do |import| extract_schema_for(import) end.join if imported_xsds.empty? add_global_namespace_to(schema) else imported_schemas = Nokogiri::XML(imported_xsds).children updated_set = imported_schemas.collect { |imported_schema| add_global_namespace_to(imported_schema) } updated_set.join end.to_s end.join end # Add namespaces from wsdl document to each schema # Sometimes schemas are defined there and referenced within the types # @return [String] String representing XML with global schemas added def add_global_namespace_to(schema) doc.parser.document.collect_namespaces.each { |namespace, value| schema[namespace] = value } schema.to_s end # Makes a GET request returning the xsd from the defined location # @return [String] Schema from location defined in 'schemaLocation' attribute def extract_schema_for(import) raise WsdlValidator::Error, "Could not find import location from #{import}" unless import['schemaLocation'] xsd_url = URI(import['schemaLocation']) xsd_url.user, xsd_url.password = basic_auth unless basic_auth.empty? request = HTTPI::Request.new(xsd_url.to_s) HTTPI.get(request).body end end