Skip to content

Commit

Permalink
Fix validation of manually added schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzobrain committed Jul 12, 2023
1 parent 2344ae7 commit e59d60a
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 27 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ group :development do
gem 'rubocop-performance', '~> 1.17'
gem 'rubocop-rake', '~> 0.6'
gem 'rubocop-rspec', '~> 2.10'
gem 'wasabi', '~> 4.0'
end
12 changes: 6 additions & 6 deletions lib/xsd/base_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@ def nodes(name = :*)
node.xpath("./xs:#{name}", { 'xs' => XML_SCHEMA })
end

# Get schema by namespace or namespace prefix
# @param [String, nil] namespace
# Get schemas by namespace or prefix
# @param [String, nil] ns_or_prefix
# @return Array<Schema>
def schemas_for_namespace(namespace)
if schema.targets_namespace?(namespace)
def schemas_for_namespace(ns_or_prefix)
if schema.targets_namespace?(ns_or_prefix)
[schema, *schema.includes.map(&:imported_schema)]
elsif (import = schema.import_by_namespace(namespace))
elsif (import = schema.import_by_namespace(ns_or_prefix))
[import.imported_schema]
else
raise Error, "Schema not found for namespace '#{namespace}' in '#{schema.id || schema.target_namespace}'"
raise Error, "Schema not found for namespace '#{ns_or_prefix}' in '#{schema.id || schema.target_namespace}'"
end
end

Expand Down
32 changes: 25 additions & 7 deletions lib/xsd/objects/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,15 @@ def import_map_children(name, cache)
end.compact.flatten
end

# Get import by namespace
# Get import by namespace or prefix
# @param [String, nil] ns_or_prefix
# @return Import
def import_by_namespace(ns)
aliases = [ns, namespaces["xmlns:#{(ns || '').gsub(/^xmlns:/, '')}"], reader.namespace_prefixes[ns]].compact
def import_by_namespace(ns_or_prefix)
aliases = [
ns_or_prefix,
namespaces["xmlns:#{(ns_or_prefix || '').gsub(/^xmlns:/, '')}"],
].compact

imports.find { |import| aliases.include?(import.namespace) }
end

Expand Down Expand Up @@ -224,12 +229,25 @@ def schema_validator
# @param [Set] processed
def recursive_import_xsd(schema, file, processed, &block)
# handle recursion
namespace = schema.target_namespace
return if processed.include?(namespace)
return if processed.include?(schema.target_namespace)

processed.add(schema.target_namespace)

# prepare schema XML with all namespaces included, clone node to avoid mutating original schema
node = schema.node
if node.namespaces.size != node.namespace_definitions.size
prefixes = node.namespace_definitions.map(&:prefix)
node = schema.node.dup

processed.add(namespace)
schema.node.namespaces.each do |attr, ns|
prefix = attr == 'xmlns' ? nil : attr.sub('xmlns:', '')
# does not work!
# node.add_namespace_definition(p, ns) unless prefixes.include?(prefix)
node[prefix] = ns unless prefixes.include?(prefix)
end
end

data = schema.node.to_xml
data = node.to_xml

schema.imports.each do |import|
name = "#{::SecureRandom.urlsafe_base64}.xsd"
Expand Down
19 changes: 5 additions & 14 deletions lib/xsd/xml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module XSD
class XML
include Generator

attr_reader :options, :object_cache, :schemas, :namespace_prefixes
attr_reader :options, :object_cache, :schemas

DEFAULT_RESOURCE_RESOLVER = proc do |location, namespace|
if location =~ /^https?:/
Expand Down Expand Up @@ -74,10 +74,9 @@ def self.open(path, **options)
end

def initialize(**options)
@options = options
@object_cache = {}
@schemas = []
@namespace_prefixes = {}
@options = options
@object_cache = {}
@schemas = []
end

def logger
Expand Down Expand Up @@ -115,24 +114,16 @@ def add_schema_node(node)
new_schema
end

# Add prefixes defined outside of processed schemas, for example in WSDL document
# @param [String] prefix
# @param [String] namespace
def add_namespace_prefix(prefix, namespace)
@namespace_prefixes[prefix] = namespace
end

# Get first added (considered primary) schema
# @return Schema, nil
def schema
schemas.first
end

# Get schema by namespace or namespace prefix
# Get schemas by namespace
# @param [String, nil] namespace
# @return Array<Schema>
def schemas_for_namespace(namespace)
namespace = namespace_prefixes[namespace] if namespace_prefixes.key?(namespace)
schemas.select { |schema| schema.target_namespace == namespace }
end

Expand Down
214 changes: 214 additions & 0 deletions spec/fixtures/mvd/service.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?xml version='1.0' encoding='UTF-8'?><!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is Metro/2.2.0-1 (tags/2.2.0u1-7139; 2012-06-02T10:55:19+0000) JAXWS-RI/2.2.6-2 JAXWS/2.2 svn-revision#unknown. -->
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsp="http://www.w3.org/ns/ws-policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:sc="http://schemas.sun.com/2006/03/wss/server" xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:tns="http://v2_4_3.incomingRequests.webservices.kernel.sx.fms.ru"
xmlns:smev="http://smev.gosuslugi.ru/rev111111" name="incomingRequests"
targetNamespace="http://v2_4_3.incomingRequests.webservices.kernel.sx.fms.ru">
<types>
<xs:schema targetNamespace="http://v2_4_3.incomingRequests.webservices.kernel.sx.fms.ru">

<xs:import namespace="http://smev.gosuslugi.ru/rev111111"
schemaLocation="http://smev-mvf.test.gosuslugi.ru:7777/gateway/services/SID0003110/1.00/wsdl/FMS001/a7bb00e0-2bab-4d65-a5e6-5a307db7308f"/>
<xs:element name="bookRequest" type="smev:serviceRequestMessage"/>
<xs:element name="bookRequestResponse" type="smev:bookRequestResponseMessage"/>

<xs:element name="getTaskTypes" type="smev:getTaskTypesMessage"/>
<xs:element name="getTaskTypesResponse" type="smev:getTaskTypesResponseMessage"/>

<xs:element name="getDictionaryValues" type="smev:getDictionaryValuesMessage"/>
<xs:element name="getDictionaryValuesResponse" type="smev:getDictionaryValuesResponseMessage"/>

<xs:element name="processTask" type="smev:serviceRequestMessage"/>
<xs:element name="processTaskResponse" type="smev:processTaskResponseMessage"/>

<xs:element name="getResult" type="smev:getResultMessage"/>
<xs:element name="getResultResponse" type="smev:getResultResponseMessage"/>

<xs:element name="sendErrorReport" type="smev:sendErrorReportMessage"/>
<xs:element name="sendErrorReportResponse" type="smev:sendErrorReportResponseMessage"/>

</xs:schema>
</types>
<message name="bookRequest">
<part name="parameters" element="tns:bookRequest"/>
</message>
<message name="bookRequestResponse">
<part name="result" element="tns:bookRequestResponse"/>
</message>

<message name="getTaskTypes">
<part name="parameters" element="tns:getTaskTypes"/>
</message>
<message name="getTaskTypesResponse">
<part name="result" element="tns:getTaskTypesResponse"/>
</message>

<message name="getDictionaryValues">
<part name="parameters" element="tns:getDictionaryValues"/>
</message>
<message name="getDictionaryValuesResponse">
<part name="result" element="tns:getDictionaryValuesResponse"/>
</message>

<message name="processTask">
<part name="parameters" element="tns:processTask"/>
</message>
<message name="processTaskResponse">
<part name="result" element="tns:processTaskResponse"/>
</message>

<message name="getResult">
<part name="parameters" element="tns:getResult"/>
</message>
<message name="getResultResponse">
<part name="result" element="tns:getResultResponse"/>
</message>

<message name="sendErrorReport">
<part name="parameters" element="tns:sendErrorReport"/>
</message>
<message name="sendErrorReportResponse">
<part name="result" element="tns:sendErrorReportResponse"/>
</message>

<portType name="incomingRequestsService_v2_4_3">
<operation name="bookRequest">
<input message="tns:bookRequest"/>
<output message="tns:bookRequestResponse"/>
</operation>
<operation name="getTaskTypes">
<input message="tns:getTaskTypes"/>
<output message="tns:getTaskTypesResponse"/>
</operation>
<operation name="getDictionaryValues">
<input message="tns:getDictionaryValues"/>
<output message="tns:getDictionaryValuesResponse"/>
</operation>
<operation name="processTask">
<input message="tns:processTask"/>
<output message="tns:processTaskResponse"/>
</operation>
<operation name="getResult">
<input message="tns:getResult"/>
<output message="tns:getResultResponse"/>
</operation>
<operation name="sendErrorReport">
<input message="tns:sendErrorReport"/>
<output message="tns:sendErrorReportResponse"/>
</operation>
</portType>
<binding name="incomingRequestsBinding" type="tns:incomingRequestsService_v2_4_3">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="bookRequest">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="getTaskTypes">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="getDictionaryValues">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="processTask">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="getResult">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="sendErrorReport">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="incomingRequests_v2_4_3">
<port name="incomingRequestsPort" binding="tns:incomingRequestsBinding">
<soap:address location="http://smev-mvf.test.gosuslugi.ru:7777/gateway/services/SID0003110/1.00"/>
</port>
</service>

<service name="incomingRequestsWSI">
<port name="incomingRequestsWSIPort" binding="tns:incomingRequestsBinding">
<wsp:PolicyReference URI="#incomingRequestsWSIBindingPolicy"/>
<soap:address location="http://smev-mvf.test.gosuslugi.ru:7777/gateway/services/SID0003110/1.00"/>
</port>
</service>

<wsp:Policy wsu:Id="incomingRequestsWSIBindingPolicy">
<wsp:ExactlyOne>
<wsp:All>
<!--
<sp:TransportBinding>
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate="false"/>
</wsp:Policy>
</sp:TransportToken>
<sp:Layout>
<wsp:Policy>
<sp:Lax/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic128/>
</wsp:Policy>
</sp:AlgorithmSuite>
</wsp:Policy>
</sp:TransportBinding>
-->
<sp:SupportingTokens>
<wsp:Policy>
<sp:UsernameToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken10/>
<sp:HashPassword/>
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>

<sp:Wss11/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
</definitions>
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'xsd'
require 'wasabi'

require_relative 'support/helper_methods'

Expand Down
14 changes: 14 additions & 0 deletions spec/xsd/schema_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,18 @@
end
end
end

context 'with mvd example files' do
describe '#validate' do

it 'validates schema from WSDL document' do
wsdl = Wasabi.document(fixture_file(%w[mvd service.wsdl]))

xsd = XSD::XML.new(logger: spec_logger)
xsd.add_schema_node(wsdl.parser.schemas.first)

expect { xsd.schema.validate }.not_to raise_error
end
end
end
end

0 comments on commit e59d60a

Please sign in to comment.