You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2017/09/01 15:03:56 UTC
[28/50] qpid-proton git commit: PROTON-1532: ruby Container support
for SASL, client and server.
PROTON-1532: ruby Container support for SASL, client and server.
- add Container.connect options for client-side SASL settings
- use on_connection_bound for server-side SASL settings based on incoming connection/transport
For example code see proton-c/bindings/ruby/tests/test_container.rb
Proper examples will be added later.
Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/36b64f73
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/36b64f73
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/36b64f73
Branch: refs/heads/go1
Commit: 36b64f73ffe7e9add93c32da622474d2ee29dce6
Parents: 0606126
Author: Alan Conway <ac...@redhat.com>
Authored: Fri Aug 11 15:55:25 2017 -0400
Committer: Alan Conway <ac...@redhat.com>
Committed: Tue Aug 22 15:41:48 2017 -0400
----------------------------------------------------------------------
proton-c/CMakeLists.txt | 11 +-
proton-c/bindings/ruby/lib/core/connection.rb | 15 +-
proton-c/bindings/ruby/lib/core/message.rb | 4 +-
proton-c/bindings/ruby/lib/core/sasl.rb | 104 ++++++----
proton-c/bindings/ruby/lib/core/transport.rb | 2 +
proton-c/bindings/ruby/lib/core/url.rb | 16 +-
.../ruby/lib/handler/endpoint_state_handler.rb | 2 +-
proton-c/bindings/ruby/lib/reactor/connector.rb | 73 ++++---
proton-c/bindings/ruby/lib/reactor/container.rb | 77 +++----
proton-c/bindings/ruby/lib/reactor/reactor.rb | 1 -
proton-c/bindings/ruby/lib/reactor/urls.rb | 7 +-
proton-c/bindings/ruby/lib/util/condition.rb | 2 +
proton-c/bindings/ruby/lib/util/swig_helper.rb | 2 +-
proton-c/bindings/ruby/tests/test_container.rb | 200 +++++++++++++++++++
proton-c/bindings/ruby/tests/test_tools.rb | 193 ++++++++++++++++++
proton-c/src/sasl/cyrus_sasl.c | 6 +-
16 files changed, 603 insertions(+), 112 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/proton-c/CMakeLists.txt b/proton-c/CMakeLists.txt
index 054a054..2bfbb27 100644
--- a/proton-c/CMakeLists.txt
+++ b/proton-c/CMakeLists.txt
@@ -792,11 +792,12 @@ find_program(RUBY_EXE "ruby")
if (RUBY_EXE AND BUILD_RUBY)
set (rb_root "${pn_test_root}/ruby")
set (rb_src "${CMAKE_CURRENT_SOURCE_DIR}/bindings/ruby")
- set (rb_lib "${CMAKE_CURRENT_SOURCE_DIR}/bindings/ruby/lib")
+ set (rb_lib "${rb_src}/lib")
+ set (rb_tests "${rb_src}/tests")
set (rb_bin "${CMAKE_CURRENT_BINARY_DIR}/bindings/ruby")
set (rb_bld "$<TARGET_FILE_DIR:qpid-proton>")
set (rb_path $ENV{PATH} ${rb_bin} ${rb_bld})
- set (rb_rubylib ${rb_root} ${rb_src} ${rb_bin} ${rb_bld} ${rb_lib})
+ set (rb_rubylib ${rb_root} ${rb_src} ${rb_bin} ${rb_bld} ${rb_lib} ${rb_tests})
to_native_path("${rb_path}" rb_path)
to_native_path("${rb_rubylib}" rb_rubylib)
@@ -806,6 +807,12 @@ if (RUBY_EXE AND BUILD_RUBY)
COMMAND ${env_py} -- "PATH=${rb_path}" "RUBYLIB=${rb_rubylib}"
${RUBY_EXE} example_test.rb -v)
+ # TODO aconway 2017-08-16: move test cmake code to ruby/tests directory
+ add_test(NAME ruby-container-test
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/proton-c/bindings/ruby
+ COMMAND ${env_py} -- "PATH=${rb_path}" "RUBYLIB=${rb_rubylib}"
+ ${RUBY_EXE} ${rb_tests}/test_container.rb -v)
+
# ruby unit tests: tests/ruby/proton-test
# only enable the tests if the Ruby gem dependencies were found
if (DEFAULT_RUBY_TESTING)
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/core/connection.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/core/connection.rb b/proton-c/bindings/ruby/lib/core/connection.rb
index c1bcaf3..949ff2a 100644
--- a/proton-c/bindings/ruby/lib/core/connection.rb
+++ b/proton-c/bindings/ruby/lib/core/connection.rb
@@ -19,7 +19,7 @@
module Qpid::Proton
- # A Connection option has at most one Qpid::Proton::Transport instance.
+ # A Connection has at most one Qpid::Proton::Transport instance.
#
class Connection < Endpoint
@@ -35,6 +35,19 @@ module Qpid::Proton
#
proton_accessor :hostname
+ # @!attribute user
+ # The user name for authentication.
+ #
+ # A client sets authentication data with the :user and :password options
+ # to {Container#connect}. On a server this returns the authenticated name
+ # from the client. It makes no sense to set this on the server side.
+ #
+ # @return [String] the user name
+ proton_accessor :user
+
+ # @private
+ proton_writer :password
+
# @private
proton_reader :attachments
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/core/message.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/core/message.rb b/proton-c/bindings/ruby/lib/core/message.rb
index 26a3ae2..81bbadb 100644
--- a/proton-c/bindings/ruby/lib/core/message.rb
+++ b/proton-c/bindings/ruby/lib/core/message.rb
@@ -129,13 +129,13 @@ module Qpid::Proton
end
# Creates a new +Message+ instance.
- def initialize
+ def initialize(body = nil)
@impl = Cproton.pn_message
ObjectSpace.define_finalizer(self, self.class.finalize!(@impl))
@properties = {}
@instructions = {}
@annotations = {}
- @body = nil
+ self.body = body unless body.nil?
end
def to_s
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/core/sasl.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/core/sasl.rb b/proton-c/bindings/ruby/lib/core/sasl.rb
index be57044..965c889 100644
--- a/proton-c/bindings/ruby/lib/core/sasl.rb
+++ b/proton-c/bindings/ruby/lib/core/sasl.rb
@@ -28,19 +28,7 @@ module Qpid::Proton
# The peer acting as the SASL server must provide authentication against the
# received credentials.
#
- # @example
- # # SCENARIO: the remote endpoint has not initialized their connection
- # # then the local endpoint, acting as a SASL server, decides
- # # to allow an anonymous connection.
- # #
- # # The SASL layer locally assumes the role of server and then
- # # enables anonymous authentication for the remote endpoint.
- # #
- # sasl = @transport.sasl
- # sasl.server
- # sasl.mechanisms("ANONYMOUS")
- # sasl.done(Qpid::Proton::SASL::OK)
- #
+ # @note Do not instantiate directly, use {Transport#sasl} to create a SASL object.
class SASL
# Negotation has not completed.
@@ -50,45 +38,89 @@ module Qpid::Proton
# Authentication failed due to bad credentials.
AUTH = Cproton::PN_SASL_AUTH
- # Constructs a new instance for the given transport.
- #
- # @param transport [Transport] The transport.
- #
- # @private A SASL should be fetched only from its Transport
- #
+ private
+
+ include Util::SwigHelper
+ PROTON_METHOD_PREFIX = "pn_sasl"
+
+ public
+
+ # @private
+ # @note Do not instantiate directly, use {Transport#sasl} to create a SASL object.
def initialize(transport)
@impl = Cproton.pn_sasl(transport.impl)
end
- # Sets the acceptable SASL mechanisms.
+ # @!attribute allow_insecure_mechs
+ # @return [Bool] true if clear text authentication is allowed on insecure connections.
+ proton_accessor :allow_insecure_mechs
+
+ # @!attribute user [r]
+ # @return [String] the authenticated user name
+ proton_reader :user
+
+ # Set the mechanisms allowed for SASL negotation
+ # @param mechanisms [String] space-delimited list of allowed mechanisms
+ def allowed_mechs=(mechanisms)
+ Cproton.pn_sasl_allowed_mechs(@impl, mechanisms)
+ end
+
+ # @deprecated use {#allowed_mechs=}
+ def mechanisms(m)
+ self.allowed_mechs = m
+ end
+
+ # True if extended SASL negotiation is supported
#
- # @param mechanisms [String] The space-delimited set of mechanisms.
+ # All implementations of Proton support ANONYMOUS and EXTERNAL on both
+ # client and server sides and PLAIN on the client side.
#
- # @example Use anonymous SASL authentication.
- # @sasl.mechanisms("GSSAPI CRAM-MD5 PLAIN")
+ # Extended SASL implememtations use an external library (Cyrus SASL)
+ # to support other mechanisms.
#
- def mechanisms(mechanisms)
- Cproton.pn_sasl_mechanisms(@impl, mechanisms)
+ # @return [Bool] true if extended SASL negotiation is supported
+ def self.extended?()
+ Cproton.pn_sasl_extended()
end
- # Returns the outcome of the SASL negotiation.
+ # Set the sasl configuration path
+ #
+ # This is used to tell SASL where to look for the configuration file.
+ # In the current implementation it can be a colon separated list of directories.
+ #
+ # The environment variable PN_SASL_CONFIG_PATH can also be used to set this path,
+ # but if both methods are used then this pn_sasl_config_path() will take precedence.
+ #
+ # If not set the underlying implementation default will be used.
#
- # @return [Integer] The outcome.
+ # @param path the configuration path
#
- def outcome
- outcome = Cprotn.pn_sasl_outcome(@impl)
- return nil if outcome == NONE
- outcome
+ def self.config_path=(path)
+ Cproton.pn_sasl_config_path(nil, path)
+ path
end
- # Set the condition of the SASL negotiation.
+ # @deprecated use {config_path=}
+ def self.config_path(path)
+ self.config_path = path
+ end
+
+ # Set the configuration file name, without extension
+ #
+ # The name with an a ".conf" extension will be searched for in the
+ # configuration path. If not set, it defaults to "proton-server" or
+ # "proton-client" for a server (incoming) or client (outgoing) connection
+ # respectively.
#
- # @param outcome [Integer] The outcome.
+ # @param name the configuration file name without extension
#
- def done(outcome)
- Cproton.pn_sasl_done(@impl, outcome)
+ def self.config_name=(name)
+ Cproton.pn_sasl_config_name(nil, name)
end
+ # @deprecated use {config_name=}
+ def self.config_name(name)
+ self.config_name = name
+ end
end
-
end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/core/transport.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/core/transport.rb b/proton-c/bindings/ruby/lib/core/transport.rb
index c3cacc7..04697a3 100644
--- a/proton-c/bindings/ruby/lib/core/transport.rb
+++ b/proton-c/bindings/ruby/lib/core/transport.rb
@@ -386,6 +386,8 @@ module Qpid::Proton
Cproton.pn_transport_tick(@impl, now)
end
+ # Create, or return existing, SSL object for the transport.
+ # @return [SASL] the SASL object
def sasl
SASL.new(self)
end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/core/url.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/core/url.rb b/proton-c/bindings/ruby/lib/core/url.rb
index 1fa1222..39b6465 100644
--- a/proton-c/bindings/ruby/lib/core/url.rb
+++ b/proton-c/bindings/ruby/lib/core/url.rb
@@ -28,11 +28,13 @@ module Qpid::Proton
attr_reader :port
attr_reader :path
+ # Parse a string, return a new URL
+ # @param url [#to_s] the URL string
def initialize(url = nil, options = {})
options[:defaults] = true
if url
- @url = Cproton.pn_url_parse(url)
+ @url = Cproton.pn_url_parse(url.to_s)
if @url.nil?
raise ::ArgumentError.new("invalid url: #{url}")
end
@@ -64,6 +66,11 @@ module Qpid::Proton
"#{@scheme}://#{@username.nil? ? '' : @username}#{@password.nil? ? '' : '@' + @password + ':'}#{@host}:#{@port}/#{@path}"
end
+ # Return self
+ def to_url()
+ self
+ end
+
private
def defaults
@@ -71,7 +78,12 @@ module Qpid::Proton
@host = @host || "0.0.0.0"
@port = @port || 5672
end
-
end
+end
+class String
+ # Convert this string to a URL
+ def to_url()
+ return URL.new(self)
+ end
end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/handler/endpoint_state_handler.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/handler/endpoint_state_handler.rb b/proton-c/bindings/ruby/lib/handler/endpoint_state_handler.rb
index 727a20b..11e970a 100644
--- a/proton-c/bindings/ruby/lib/handler/endpoint_state_handler.rb
+++ b/proton-c/bindings/ruby/lib/handler/endpoint_state_handler.rb
@@ -119,7 +119,7 @@ module Qpid::Proton::Handler
end
def on_connection_opened(event)
- Qpid::Proton::Event.dispatch(@delegate, :on_session_opened, event) if !@delegate.nil?
+ Qpid::Proton::Event.dispatch(@delegate, :on_connection_opened, event) if !@delegate.nil?
end
def on_session_opened(event)
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/reactor/connector.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/reactor/connector.rb b/proton-c/bindings/ruby/lib/reactor/connector.rb
index a6523db..0971141 100644
--- a/proton-c/bindings/ruby/lib/reactor/connector.rb
+++ b/proton-c/bindings/ruby/lib/reactor/connector.rb
@@ -21,16 +21,24 @@ module Qpid::Proton::Reactor
class Connector < Qpid::Proton::BaseHandler
- attr_accessor :address
- attr_accessor :reconnect
- attr_accessor :ssl_domain
+ def initialize(connection, url, opts)
+ @connection, @opts = connection, opts
+ @urls = URLs.new(url) if url
+ opts.each do |k,v|
+ case k
+ when :url, :urls, :address
+ @urls = URLs.new(v) unless @urls
+ when :reconnect
+ @reconnect = v
+ end
+ end
+ raise ::ArgumentError.new("no url for connect") unless @urls
- def initialize(connection)
- @connection = connection
- @address = nil
- @heartbeat = nil
- @reconnect = nil
- @ssl_domain = nil
+ # TODO aconway 2017-08-17: review reconnect configuration and defaults
+ @reconnect = Backoff.new() unless @reconnect
+ @ssl_domain = SessionPerConnection.new # TODO seems this should be configurable
+ @connection.overrides = self
+ @connection.open
end
def on_connection_local_open(event)
@@ -38,10 +46,7 @@ module Qpid::Proton::Reactor
end
def on_connection_remote_open(event)
- if !@reconnect.nil?
- @reconnect.reset
- @transport = nil
- end
+ @reconnect.reset if @reconnect
end
def on_transport_tail_closed(event)
@@ -73,26 +78,38 @@ module Qpid::Proton::Reactor
end
def connect(connection)
- url = @address.next
+ url = @urls.next
+ transport = Qpid::Proton::Transport.new
+ @opts.each do |k,v|
+ case k
+ when :user
+ connection.user = v
+ when :password
+ connection.password = v
+ when :heartbeat
+ transport.idle_timeout = v.to_i
+ when :idle_timeout
+ transport.idle_timeout = v.(v*1000).to_i
+ when :sasl_enabled
+ transport.sasl if v
+ when :sasl_allow_insecure_mechs
+ transport.sasl.allow_insecure_mechs = v
+ when :sasl_allowed_mechs, :sasl_mechanisms
+ transport.sasl.allowed_mechs = v
+ end
+ end
+
+ # TODO aconway 2017-08-11: hostname setting is incorrect, reactor only
connection.hostname = "#{url.host}:#{url.port}"
+ connection.user = url.username if url.username && !url.username.empty?
+ connection.password = url.password if url.password && !url.password.empty?
- transport = Qpid::Proton::Transport.new
transport.bind(connection)
- if !@heartbeat.nil?
- transport.idle_timeout = @heartbeat
- elsif (url.scheme == "amqps") && !@ssl_domain.nil?
+
+ if (url.scheme == "amqps") && @ssl_domain
@ssl = Qpid::Proton::SSL.new(transport, @ssl_domain)
- @ss.peer_hostname = url.host
- elsif !url.username.nil?
- sasl = transport.sasl
- if url.username == "anonymous"
- sasl.mechanisms("ANONYMOUS")
- else
- sasl.plain(url.username, url.password)
- end
+ @ssl.peer_hostname = url.host
end
end
-
end
-
end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/reactor/container.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/reactor/container.rb b/proton-c/bindings/ruby/lib/reactor/container.rb
index 2a7a030..aa1b303 100644
--- a/proton-c/bindings/ruby/lib/reactor/container.rb
+++ b/proton-c/bindings/ruby/lib/reactor/container.rb
@@ -19,7 +19,7 @@
module Qpid::Proton::Reactor
- # @private
+ private
class InternalTransactionHandler < Qpid::Proton::Handler::OutgoingMessageHandler
def initialize
@@ -35,7 +35,7 @@ module Qpid::Proton::Reactor
end
-
+ public
# A representation of the AMQP concept of a container which, loosely
# speaking, is something that establishes links to or from another
# container on which messages are transferred.
@@ -74,42 +74,49 @@ module Qpid::Proton::Reactor
end
end
- # Initiates the establishment of an AMQP connection.
+ # TODO aconway 2017-08-17: fill out options
+
+ # Connects to a remote AMQP endpoint and sends an AMQP "open" frame.
#
- # @param options [Hash] A hash of named arguments.
+ # @param url [#to_url] Connect to URL host:port.
+ # If URL has user:password use them for authentication.
#
- def connect(options = {})
- conn = self.connection(options[:handler])
- conn.container = self.container_id || generate_uuid
- connector = Connector.new(conn)
- conn.overrides = connector
- if !options[:url].nil?
- connector.address = URLs.new([options[:url]])
- elsif !options[:urls].nil?
- connector.address = URLs.new(options[:urls])
- elsif !options[:address].nil?
- connector.address = URLs.new([Qpid::Proton::URL.new(options[:address])])
- else
- raise ::ArgumentError.new("either :url or :urls or :address required")
- end
-
- connector.heartbeat = options[:heartbeat] if !options[:heartbeat].nil?
- if !options[:reconnect].nil?
- connector.reconnect = options[:reconnect]
- else
- connector.reconnect = Backoff.new()
+ # @option opts [String] :user user name for authentication if not given by URL
+ # @option opts [String] :password password for authentication if not given by URL
+ #
+ # @option opts [Numeric] :idle_timeout seconds before closing an idle connection
+ #
+ # @option opts [Boolean] :sasl_enabled Enable or disable SASL.
+ #
+ # @option opts [Boolean] :sasl_allow_insecure_mechs Allow mechanisms that disclose clear text
+ # passwords, even over an insecure connection. By default, such mechanisms are only allowed
+ # when SSL is enabled.
+ #
+ # @option opts [String] :sasl_allowed_mechs the allowed SASL mechanisms for use on the connection.
+ #
+ # @param url [Hash] *deprecated* if url is a Hash and opts is unspecified, treat it as opts.
+ # @option opts [#to_url] :url *deprecated* use the url parameter
+ # @option opts [Enumerable<#to_url>] :urls *deprecated* use the url parameter
+ # @option opts [#to_url] :address *deprecated* use the url parameter
+ # @option opts [#to_url] :heartbeat *deprecated* alias for :idle_timeout, but in milliseconds
+ # @return [Connection] the new connection
+ #
+ def connect(url, opts = {})
+ # Backwards compatible with old connect(options)
+ if url.is_a? Hash and opts.empty?
+ opts = url
+ url = nil
end
-
- connector.ssl_domain = SessionPerConnection.new # TODO seems this should be configurable
-
- conn.open
-
+ conn = self.connection(opts[:handler])
+ conn.container = self.container_id || generate_uuid
+ connector = Connector.new(conn, url, opts)
return conn
end
+ private
def _session(context)
if context.is_a?(Qpid::Proton::URL)
- return self._session(self.connect(:url => context))
+ return _session(self.connect(:url => context))
elsif context.is_a?(Qpid::Proton::Session)
return context
elsif context.is_a?(Qpid::Proton::Connection)
@@ -123,6 +130,7 @@ module Qpid::Proton::Reactor
end
end
+ public
# Initiates the establishment of a link over which messages can be sent.
#
# @param context [String, URL] The context.
@@ -146,7 +154,7 @@ module Qpid::Proton::Reactor
target = context.path
end
- session = self._session(context)
+ session = _session(context)
sender = session.sender(opts[:name] ||
id(session.connection.container,
@@ -155,7 +163,7 @@ module Qpid::Proton::Reactor
sender.target.address = target if target
sender.handler = opts[:handler] if !opts[:handler].nil?
sender.tag_generator = opts[:tag_generator] if !opts[:tag_gnenerator].nil?
- self._apply_link_options(opts[:options], sender)
+ _apply_link_options(opts[:options], sender)
sender.open
return sender
end
@@ -192,7 +200,7 @@ module Qpid::Proton::Reactor
source = context.path
end
- session = self._session(context)
+ session = _session(context)
receiver = session.receiver(opts[:name] ||
id(session.connection.container,
@@ -201,7 +209,7 @@ module Qpid::Proton::Reactor
receiver.source.dynamic = true if opts.has_key?(:dynamic) && opts[:dynamic]
receiver.target.address = opts[:target] if !opts[:target].nil?
receiver.handler = opts[:handler] if !opts[:handler].nil?
- self._apply_link_options(opts[:options], receiver)
+ _apply_link_options(opts[:options], receiver)
receiver.open
return receiver
end
@@ -236,6 +244,7 @@ module Qpid::Proton::Reactor
return acceptor
end
+ private
def do_work(timeout = nil)
self.timeout = timeout unless timeout.nil?
self.process
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/reactor/reactor.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/reactor/reactor.rb b/proton-c/bindings/ruby/lib/reactor/reactor.rb
index a84a716..f612876 100644
--- a/proton-c/bindings/ruby/lib/reactor/reactor.rb
+++ b/proton-c/bindings/ruby/lib/reactor/reactor.rb
@@ -115,7 +115,6 @@ module Qpid::Proton::Reactor
end
def run(&block)
- self.timeout = 3.14159265359
self.start
while self.process do
if block_given?
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/reactor/urls.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/reactor/urls.rb b/proton-c/bindings/ruby/lib/reactor/urls.rb
index 8cdb16c..44fd956 100644
--- a/proton-c/bindings/ruby/lib/reactor/urls.rb
+++ b/proton-c/bindings/ruby/lib/reactor/urls.rb
@@ -22,7 +22,12 @@ module Qpid::Proton::Reactor
class URLs
def initialize(values)
- @values = [values].flatten
+ @values = values
+ if @values.is_a? Enumerable
+ @values = @values.map { |u| u.to_url }
+ else
+ @values = [values.to_url]
+ end
@iter = @values.each
end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/util/condition.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/util/condition.rb b/proton-c/bindings/ruby/lib/util/condition.rb
index b8fd94b..ad49595 100644
--- a/proton-c/bindings/ruby/lib/util/condition.rb
+++ b/proton-c/bindings/ruby/lib/util/condition.rb
@@ -21,6 +21,8 @@ module Qpid::Proton::Util
class Condition
+ attr_reader :name, :description, :info
+
def initialize(name, description = nil, info = nil)
@name = name
@description = description
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/lib/util/swig_helper.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/lib/util/swig_helper.rb b/proton-c/bindings/ruby/lib/util/swig_helper.rb
index d60e9e4..5567235 100644
--- a/proton-c/bindings/ruby/lib/util/swig_helper.rb
+++ b/proton-c/bindings/ruby/lib/util/swig_helper.rb
@@ -86,7 +86,7 @@ module Qpid::Proton::Util
proton_method = "#{self::PROTON_METHOD_PREFIX}_#{name}"
# drop the trailing '?' if this is a property method
proton_method = proton_method[0..-2] if proton_method.end_with? "?"
- create_wrapper_method(name, proton_method)
+ create_wrapper_method(name, proton_method, options[:arg])
end
def proton_writer(name, options = {})
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/tests/test_container.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/tests/test_container.rb b/proton-c/bindings/ruby/tests/test_container.rb
new file mode 100644
index 0000000..7ed81b2
--- /dev/null
+++ b/proton-c/bindings/ruby/tests/test_container.rb
@@ -0,0 +1,200 @@
+#--
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#++
+
+require 'test_tools'
+
+Message = Qpid::Proton::Message
+SASL = Qpid::Proton::SASL
+URL = Qpid::Proton::URL
+
+class ContainerTest < Minitest::Test
+
+ # Send n messages
+ class SendMessageClient < TestHandler
+ attr_reader :accepted
+
+ def initialize(url, link_name, body)
+ super()
+ @url, @link_name, @message = url, link_name, Message.new(body)
+ end
+
+ def on_start(event)
+ event.container.create_sender(@url, {:name => @link_name})
+ end
+
+ def on_sendable(event)
+ if event.sender.credit > 0
+ event.sender.send(@message)
+ end
+ end
+
+ def on_accepted(event)
+ @accepted = event
+ event.connection.close
+ end
+ end
+
+ def test_simple()
+ TestServer.new.run do |s|
+ lname = "test-link"
+ body = "hello"
+ c = SendMessageClient.new(s.addr, lname, body).run
+ assert_instance_of(Qpid::Proton::Event::Event, c.accepted)
+ assert_equal(lname, s.links.pop(true).name)
+ assert_equal(body, s.messages.pop(true).body)
+ end
+ end
+
+end
+
+class ContainerSASLTest < Minitest::Test
+
+ # Connect to URL using mechanisms and insecure to configure the transport
+ class SASLClient < TestHandler
+
+ def initialize(url, opts={})
+ super()
+ @url, @opts = url, opts
+ end
+
+ def on_start(event)
+ event.container.connect(@url, @opts)
+ end
+
+ def on_connection_opened(event)
+ super
+ event.container.stop
+ end
+ end
+
+ # Server with SASL settings
+ class SASLServer < TestServer
+ def initialize(mechanisms=nil, insecure=nil, realm=nil)
+ super()
+ @mechanisms, @insecure, @realm = mechanisms, insecure, realm
+ end
+
+ def on_connection_bound(event)
+ sasl = event.transport.sasl
+ sasl.allow_insecure_mechs = @insecure unless @insecure.nil?
+ sasl.allowed_mechs = @mechanisms unless @mechanisms.nil?
+ # TODO aconway 2017-08-16: need `sasl.realm(@realm)` here for non-default realms.
+ # That reqiures pn_sasl_set_realm() at the C layer - the realm should
+ # be passed to cyrus_sasl_init_server()
+ end
+ end
+
+ # Generate SASL server configuration files and database, initialize proton SASL
+ class SASLConfig
+ attr_reader :conf_dir, :conf_file, :conf_name, :database
+
+ def initialize()
+ if SASL.extended? # Configure cyrus SASL
+ @conf_dir = File.expand_path('sasl_conf')
+ @conf_name = "proton-server"
+ @database = File.join(@conf_dir, "proton.sasldb")
+ @conf_file = File.join(conf_dir,"#{@conf_name}.conf")
+ Dir::mkdir(@conf_dir) unless File.directory?(@conf_dir)
+ # Same user name in different realms
+ make_user("user", "password", "proton") # proton realm
+ make_user("user", "default_password") # Default realm
+ File.open(@conf_file, 'w') do |f|
+ f.write("
+sasldb_path: #{database}
+mech_list: EXTERNAL DIGEST-MD5 SCRAM-SHA-1 CRAM-MD5 PLAIN ANONYMOUS
+")
+ end
+ # Tell proton library to use the new configuration
+ SASL.config_path(conf_dir)
+ SASL.config_name(conf_name)
+ end
+ end
+
+ private
+
+ SASLPASSWD = (ENV['SASLPASSWD'] or 'saslpasswd2')
+
+ def make_user(user, password, realm=nil)
+ realm_opt = (realm ? "-u #{realm}" : "")
+ cmd = "echo '#{password}' | #{SASLPASSWD} -c -p -f #{database} #{realm_opt} #{user}"
+ system(cmd) or raise RuntimeError.new("saslpasswd2 failed: #{makepw_cmd}")
+ end
+ DEFAULT = SASLConfig.new
+ end
+
+ def test_sasl_anonymous()
+ SASLServer.new("ANONYMOUS").run do |s|
+ c = SASLClient.new(s.addr, {:sasl_allowed_mechs => "ANONYMOUS"}).run
+ refute_empty(c.connections)
+ refute_empty(s.connections)
+ assert_nil(s.connections.pop(true).user)
+ end
+ end
+
+ def test_sasl_plain_url()
+ # Use default realm with URL, should authenticate with "default_password"
+ SASLServer.new("PLAIN", true).run do |s|
+ c = SASLClient.new("amqp://user:default_password@#{s.addr}",
+ {:sasl_allowed_mechs => "PLAIN", :sasl_allow_insecure_mechs => true}).run
+ refute_empty(c.connections)
+ refute_empty(s.connections)
+ sc = s.connections.pop(true)
+ assert_equal("user", sc.transport.sasl.user)
+ end
+ end
+
+ def test_sasl_plain_options()
+ # Use default realm with connection options, should authenticate with "default_password"
+ SASLServer.new("PLAIN", true).run do |s|
+ c = SASLClient.new(s.addr,
+ {:user => "user", :password => "default_password",
+ :sasl_allowed_mechs => "PLAIN", :sasl_allow_insecure_mechs => true}).run
+ refute_empty(c.connections)
+ refute_empty(s.connections)
+ sc = s.connections.pop(true)
+ assert_equal("user", sc.transport.sasl.user)
+ end
+ end
+
+ # Test disabled, see on_connection_bound - missing realm support in proton C.
+ def TODO_test_sasl_plain_realm()
+ # Use the non-default proton realm on the server, should authenticate with "password"
+ SASLServer.new("PLAIN", true, "proton").run do |s|
+ c = SASLClient.new("amqp://user:password@#{s.addr}",
+ {:sasl_allowed_mechs => "PLAIN", :sasl_allow_insecure_mechs => true}).run
+ refute_empty(c.connections)
+ refute_empty(s.connections)
+ sc = s.connections.pop(true)
+ assert_equal("user", sc.transport.sasl.user)
+ end
+ end
+
+ # Ensure we don't allow PLAIN if allow_insecure_mechs = true is not explicitly set
+ def test_disallow_insecure()
+ # Don't set allow_insecure_mechs, but try to use PLAIN
+ SASLServer.new("PLAIN", nil).run(true) do |s|
+ begin
+ SASLClient.new("amqp://user:password@#{s.addr}",
+ {:sasl_allowed_mechs => "PLAIN", :sasl_allow_insecure_mechs => true}).run
+ rescue TestError => e
+ assert_match(/PN_TRANSPORT_ERROR.*unauthorized-access/, e.to_s)
+ end
+ end
+ end
+end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/bindings/ruby/tests/test_tools.rb
----------------------------------------------------------------------
diff --git a/proton-c/bindings/ruby/tests/test_tools.rb b/proton-c/bindings/ruby/tests/test_tools.rb
new file mode 100644
index 0000000..a48a508
--- /dev/null
+++ b/proton-c/bindings/ruby/tests/test_tools.rb
@@ -0,0 +1,193 @@
+#--
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#++
+
+# Tools for tests
+
+require 'minitest/autorun'
+require 'qpid_proton'
+require 'thread'
+require 'socket'
+
+Container = Qpid::Proton::Reactor::Container
+MessagingHandler = Qpid::Proton::Handler::MessagingHandler
+
+# Bind an unused local port using bind(0) and SO_REUSEADDR and hold it till close()
+# Provides #host, #port and #addr ("host:port") as strings
+class TestPort
+ attr_reader :host, :port, :addr
+
+ # With block, execute block passing self then close
+ # Note host must be the local host, but you can pass '::1' instead for ipv6
+ def initialize(host='127.0.0.1')
+ @sock = Socket.new(:INET, :STREAM)
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
+ @sock.bind(Socket.sockaddr_in(0, host))
+ @host, @port = @sock.connect_address.ip_unpack
+ @addr = "#{@host}:#{@port}"
+ if block_given?
+ begin
+ yield self
+ ensure
+ close
+ end
+ end
+ end
+
+ def close
+ @sock.close()
+ end
+end
+
+class TestError < Exception; end
+
+# Handler that creates its own container to run itself, and records some common
+# events that are checked by tests
+class TestHandler < MessagingHandler
+
+ # Record errors and successfully opened endpoints
+ attr_reader :errors, :connections, :sessions, :links, :messages
+
+ # Pass optional extra handlers and options to the Container
+ def initialize(handlers=[], options={})
+ super()
+ # Use Queue so the values can be extracted in a thread-safe way during or after a test.
+ @errors, @connections, @sessions, @links, @messages = (1..5).collect { Queue.new }
+ @container = Container.new([self]+handlers, options)
+ end
+
+ # Run the handlers container, return self.
+ # Raise an exception for server errors unless no_raise is true.
+ def run(no_raise=false)
+ @container.run
+ raise_errors unless no_raise
+ self
+ end
+
+ # If the handler has errors, raise a TestError with all the error text
+ def raise_errors()
+ return if @errors.empty?
+ text = ""
+ while @errors.size > 0
+ text << @errors.pop + "\n"
+ end
+ raise TestError.new("TestServer has errors:\n #{text}")
+ end
+
+ # TODO aconway 2017-08-15: implement in MessagingHandler
+ def on_error(event, endpoint)
+ @errors.push "#{event.type}: #{endpoint.condition.name}: #{endpoint.condition.description}"
+ raise_errors
+ end
+
+ def on_transport_error(event)
+ on_error(event, event.transport)
+ end
+
+ def on_connection_error(event)
+ on_error(event, event.condition)
+ end
+
+ def on_session_error(event)
+ on_error(event, event.session)
+ end
+
+ def on_link_error(event)
+ on_error(event, event.link)
+ end
+
+ def on_opened(queue, endpoint)
+ queue.push(endpoint)
+ endpoint.open
+ end
+
+ def on_connection_opened(event)
+ on_opened(@connections, event.connection)
+ end
+
+ def on_session_opened(event)
+ on_opened(@sessions, event.session)
+ end
+
+ def on_link_opened(event)
+ on_opened(@links, event.link)
+ end
+
+ def on_message(event)
+ @messages.push(event.message)
+ end
+end
+
+# A TestHandler that runs itself in a thread and listens on a TestPort
+class TestServer < TestHandler
+ attr_reader :host, :port, :addr
+
+ # Pass optional handlers, options to the container
+ def initialize(handlers=[], options={})
+ super
+ @tp = TestPort.new
+ @host, @port, @addr = @tp.host, @tp.port, @tp.addr
+ @listening = false
+ @ready = Queue.new
+ end
+
+ # Start server thread
+ def start(no_raise=false)
+ @thread = Thread.new do
+ begin
+ @container.listen(addr)
+ @container.run
+ rescue TestError
+ ready.push :error
+ rescue => e
+ msg = "TestServer run raised: #{e.message}\n#{e.backtrace.join("\n")}"
+ @errors << msg
+ @ready.push(:error)
+ # TODO aconway 2017-08-22: container.stop - doesn't stop the thread.
+ end
+ end
+ raise_errors unless @ready.pop == :listening or no_raise
+ end
+
+ # Stop server thread
+ def stop(no_raise=false)
+ @container.stop
+ if not @errors.empty?
+ @thread.kill
+ else
+ @thread.join
+ end
+ @tp.close
+ raise_errors unless no_raise
+ end
+
+ # start(), execute block with self, stop()
+ def run(no_raise=false)
+ begin
+ start(no_raise)
+ yield self
+ ensure
+ stop(no_raise)
+ end
+ end
+
+ def on_start(event)
+ @ready.push :listening
+ @listening = true
+ end
+end
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/36b64f73/proton-c/src/sasl/cyrus_sasl.c
----------------------------------------------------------------------
diff --git a/proton-c/src/sasl/cyrus_sasl.c b/proton-c/src/sasl/cyrus_sasl.c
index 88bdd7a..ab6eba6 100644
--- a/proton-c/src/sasl/cyrus_sasl.c
+++ b/proton-c/src/sasl/cyrus_sasl.c
@@ -180,9 +180,9 @@ void pn_sasl_config_name(pn_sasl_t *sasl0, const char *name)
void pn_sasl_config_path(pn_sasl_t *sasl0, const char *dir)
{
- if (!pni_cyrus_config_dir) {
- pni_cyrus_config_dir = strdup(dir);
- }
+ if (!pni_cyrus_config_dir) {
+ pni_cyrus_config_dir = strdup(dir);
+ }
}
__attribute__((destructor))
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org