require_relative 'error' # Helps to validate xml against schemas contained in a WSDL class WsdlValidator 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.schemas = Nokogiri::XML::Schema(parse_wsdl_schemas) rescue Wasabi::Resolver::HTTPError => e raise WsdlValidator::Error, "Unauthorized for basic auth #{basic_auth}" if e.response.code == 401 raise e 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 # @param [String, Nokogiri::XML::NodeSet] xml # @return [Boolean] Whether xml is valid according to WSDL of class def valid?(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 validator = schemas.validate(xml_under_test) raise WsdlValidator::Error, validator.join unless validator.empty? true end private # Returns a string used to prefix a URL for basic auth # @return [Array] Format is 'user_name:password@' or empty string 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] Schemas 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 schema = Nokogiri::XML(imported_xsds).children unless imported_xsds.empty? schema.to_s end.join 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