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