omf_common 6.0.7.1 → 6.0.8.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,7 @@ require 'omf_common/auth'
11
11
  module OmfCommon::Auth
12
12
 
13
13
  class MissingPrivateKeyException < AuthException; end
14
+ class MissingCertificateException < AuthException; end
14
15
 
15
16
  class CertificateStore
16
17
  include MonitorMixin
@@ -29,44 +30,56 @@ module OmfCommon::Auth
29
30
  @@instance
30
31
  end
31
32
 
32
- def register(certificate, address = nil)
33
+ def register_trusted(certificate)
33
34
  @@instance.synchronize do
34
35
  begin
35
- @x509_store.add_cert(certificate.to_x509) if @certs[address].nil? && @certs[certificate.subject].nil?
36
+ @x509_store.add_cert(certificate.to_x509)
36
37
  rescue OpenSSL::X509::StoreError => e
37
38
  if e.message == "cert already in hash table"
38
- raise "X509 cert '#{address}' already registered in X509 store"
39
+ warn "X509 cert '#{certificate.subject}' already registered in X509 store"
39
40
  else
40
41
  raise e
41
42
  end
42
43
  end
44
+ @certs[certificate.subject] ||= certificate
45
+ end
46
+ end
43
47
 
44
- address ||= certificate.address
48
+ def register(certificate)
49
+ raise "Expected Certificate, but got '#{certificate.class}'" unless certificate.is_a? Certificate
45
50
 
46
- if address
47
- @certs[address] ||= certificate
48
- else
49
- debug "Register certificate without address - #{certificate}, is it a CA cert?"
51
+ debug "Registering certificate for '#{certificate.addresses}' - #{certificate.subject}"
52
+ @@instance.synchronize do
53
+ _set(certificate.subject, certificate)
54
+ if rid = certificate.resource_id
55
+ _set(rid, certificate)
56
+ end
57
+ certificate.addresses.each do |type, name|
58
+ _set(name, certificate)
50
59
  end
51
-
52
- @certs[certificate.subject] ||= certificate
53
60
  end
54
61
  end
55
62
 
56
- def register_x509(cert_pem, address = nil)
57
- if (cert = Certificate.create_from_x509(cert_pem))
63
+ def register_x509(cert_pem)
64
+ if (cert = Certificate.create_from_pem(cert_pem))
58
65
  debug "REGISTERED #{cert}"
59
- register(cert, address)
66
+ register(cert)
60
67
  end
61
68
  end
62
69
 
63
70
  def cert_for(url)
64
- @certs[url]
71
+ # The key of @certs could be a OpenSSL::X509::Name instance
72
+ unless (cert = @certs.find { |k, v| k.to_s == url.to_s })
73
+ warn "Unknown cert '#{url}'"
74
+ raise MissingCertificateException.new(url)
75
+ end
76
+ cert[1]
65
77
  end
66
78
 
67
79
  # @param [OpenSSL::X509::Certificate] cert
68
80
  #
69
81
  def verify(cert)
82
+ #puts "VERIFY: #{cert}::#{cert.class}}"
70
83
  cert = cert.to_x509 if cert.kind_of? OmfCommon::Auth::Certificate
71
84
  v_result = @x509_store.verify(cert)
72
85
  warn "Cert verification failed: '#{@x509_store.error_string}'" unless v_result
@@ -79,7 +92,7 @@ module OmfCommon::Auth
79
92
  #
80
93
  def register_default_certs(folder)
81
94
  Dir["#{folder}/*"].each do |cert|
82
- register_x509(File.read(cert))
95
+ register_trusted(Certificate.create_from_pem(File.read(cert)))
83
96
  end
84
97
  end
85
98
 
@@ -97,6 +110,17 @@ module OmfCommon::Auth
97
110
 
98
111
  super()
99
112
  end
113
+
114
+ def _set(name, certificate)
115
+ if old = @certs[name]
116
+ return if old.to_pem == certificate.to_pem
117
+ warn "Overriding certificate '#{name}' - new: #{certificate.subject} old: #{old.subject}"
118
+ end
119
+ @certs[name] = certificate
120
+ unless name.is_a? String
121
+ _set(name.to_s, certificate)
122
+ end
123
+ end
100
124
  end # class
101
125
 
102
126
  end # module
@@ -0,0 +1,69 @@
1
+
2
+ require 'json'
3
+ require 'json/jwt'
4
+
5
+ module OmfCommon::Auth
6
+ class JWTAuthenticator
7
+
8
+ def self.sign(content, signer, signer_name = signer.subject)
9
+ msg = {cnt: content, iss: signer_name.to_s}
10
+ JSON::JWT.new(msg).sign(signer.key , :RS256).to_s
11
+ end
12
+
13
+ def self.parse(jwt_string)
14
+ jwt_string = jwt_string.split.join
15
+ # Code lifted from 'json-jwt-0.4.3/lib/json/jwt.rb'
16
+ case jwt_string.count('.')
17
+ when 2 # JWT / JWS
18
+ header, claims, signature = jwt_string.split('.', 3).collect do |segment|
19
+ UrlSafeBase64.decode64 segment.to_s
20
+ end
21
+ header, claims = [header, claims].collect do |json|
22
+ #MultiJson.load(json).with_indifferent_access
23
+ puts "JSON>>> #{json}"
24
+ JSON.parse(json, :symbolize_names => true)
25
+ end
26
+ signature_base_string = jwt_string.split('.')[0, 2].join('.')
27
+ jwt = JSON::JWT.new claims
28
+ jwt.header = header
29
+ jwt.signature = signature
30
+
31
+ # NOTE:
32
+ # Some JSON libraries generates wrong format of JSON (spaces between keys and values etc.)
33
+ # So we need to use raw base64 strings for signature verification.
34
+ unless issuer = claims[:iss]
35
+ warn "JWT: Message is missing :iss element"
36
+ return nil
37
+ end
38
+ # if cert_pem = claims[:crt]
39
+ # # let's the credential store take care of it
40
+ # OmfCommon::Auth::CertificateStore.instance.register_x509(cert_pem, src)
41
+ # end
42
+ cert = nil
43
+ issuer.split(',').compact.select do |addr|
44
+ begin
45
+ cert = OmfCommon::Auth::CertificateStore.instance.cert_for(addr)
46
+ rescue OmfCommon::Auth::MissingCertificateException
47
+ nil
48
+ end
49
+ end
50
+ unless cert
51
+ warn "JWT: Can't find cert for issuer '#{issuer}'"
52
+ return nil
53
+ end
54
+
55
+ unless OmfCommon::Auth::CertificateStore.instance.verify(cert)
56
+ warn "JWT: Invalid certificate '#{cert.to_s}', NOT signed by CA certs, or its CA cert NOT loaded into cert store."
57
+ end
58
+
59
+ jwt.verify signature_base_string, cert.to_x509.public_key
60
+ #JSON.parse(claims[:cnt], :symbolize_names => true)
61
+ claims[:cnt]
62
+ else
63
+ warn('JWT: Invalid Format. JWT should include 2 or 3 dots.')
64
+ return nil
65
+ end
66
+ end
67
+
68
+ end # class
69
+ end # module
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2012 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
6
+ require 'omf_common/auth'
7
+
8
+ module OmfCommon::Auth::PDP
9
+ class TestPDP
10
+
11
+ def initialize(opts = {})
12
+ puts "AUTH INIT>>> #{opts}"
13
+ end
14
+
15
+ def authorize(msg, &block)
16
+ puts "AUTH(#{msg.issuer})>>> #{msg}"
17
+ sender = msg.src.address
18
+ msg
19
+ end
20
+ end
21
+ end
@@ -67,7 +67,7 @@ module OmfCommon
67
67
  end
68
68
  @@instance = inst
69
69
  mopts = provider[:message_provider]
70
- mopts[:authenticate] = (opts[:auth] != nil)
70
+ mopts[:authenticate] = opts[:auth]
71
71
  Message.init(mopts)
72
72
 
73
73
  if aopts = opts[:auth]
@@ -132,6 +132,13 @@ module OmfCommon
132
132
  { proto: nil, user: nil, domain: nil }
133
133
  end
134
134
 
135
+ # Return a valid address for this type of communicator
136
+ # Must be implemented by subclasses
137
+ #
138
+ def string_to_address(a_string)
139
+ raise NotImplementedError
140
+ end
141
+
135
142
  # Subscribe to a pubsub topic
136
143
  #
137
144
  # @param [String, Array] topic_name Pubsub topic name
@@ -41,8 +41,14 @@ module OmfCommon
41
41
  { proto: :amqp, user: ::AMQP.settings[:user], domain: ::AMQP.settings[:host] }
42
42
  end
43
43
 
44
+ def string_to_address(a_string)
45
+ @address_prefix+a_string
46
+ end
47
+
44
48
  # Shut down comms layer
45
49
  def disconnect(opts = {})
50
+ info "Disconnecting..."
51
+ OmfCommon.eventloop.stop
46
52
  end
47
53
 
48
54
  # TODO: Should be thread safe and check if already connected
@@ -66,7 +72,7 @@ module OmfCommon
66
72
  #
67
73
  # @param [String] topic Pubsub topic name
68
74
  def create_topic(topic, opts = {})
69
- raise "Topic can't be nil or empty" if topic.nil? || topic.empty?
75
+ raise "Topic can't be nil or empty" if topic.nil? || topic.to_s.empty?
70
76
  opts = opts.dup
71
77
  opts[:communicator] = self
72
78
  topic = topic.to_s
@@ -0,0 +1,29 @@
1
+ # Copyright (c) 2013 National ICT Australia Limited (NICTA).
2
+ # This software may be used and distributed solely under the terms of the MIT license (License).
3
+ # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
+ # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
+
6
+ require 'oml4r'
7
+
8
+ module OmfCommon
9
+ class Comm
10
+ class AMQP
11
+
12
+ class MPPublished < OML4R::MPBase
13
+ name :amqp_published
14
+ param :time, :type => :double
15
+ param :topic, :type => :string
16
+ param :mid, :type => :string
17
+ end
18
+
19
+ class MPReceived < OML4R::MPBase
20
+ name :amqp_received
21
+ param :time, :type => :double
22
+ param :topic, :type => :string
23
+ param :mid, :type => :string
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
@@ -3,7 +3,7 @@
3
3
  # You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
4
4
  # By downloading or using this software you accept the terms and the liability disclaimer in the License.
5
5
 
6
-
6
+ require 'omf_common/comm/amqp/amqp_mp'
7
7
 
8
8
  module OmfCommon
9
9
  class Comm
@@ -11,7 +11,7 @@ module OmfCommon
11
11
  class Topic < OmfCommon::Comm::Topic
12
12
 
13
13
  def to_s
14
- "AMQP::Topic<#{id}>"
14
+ @address
15
15
  end
16
16
 
17
17
  def address
@@ -70,6 +70,7 @@ module OmfCommon
70
70
  queue.subscribe do |headers, payload|
71
71
  #puts "===(#{id}) Incoming message '#{headers.content_type}'"
72
72
  debug "Received message on #{@address}"
73
+ MPReceived.inject(Time.now.to_f, @address, payload.to_s[/mid\":\"(.{36})/, 1]) if OmfCommon::Measure.enabled?
73
74
  Message.parse(payload, headers.content_type) do |msg|
74
75
  #puts "---(#{id}) Parsed message '#{msg}'"
75
76
  on_incoming_message(msg)
@@ -93,6 +94,7 @@ module OmfCommon
93
94
  debug "(#{id}) Send message (#{content_type}) #{msg.inspect}"
94
95
  if @exchange
95
96
  @exchange.publish(content, content_type: content_type, message_id: msg.mid)
97
+ MPPublished.inject(Time.now.to_f, @address, msg.mid) if OmfCommon::Measure.enabled?
96
98
  else
97
99
  warn "Unavailable AMQP channel. Dropping message '#{msg}'"
98
100
  end
@@ -10,35 +10,35 @@ module OmfCommon
10
10
  class Local
11
11
  class Topic < OmfCommon::Comm::Topic
12
12
  @@marshall_messages = true
13
-
13
+
14
14
  # If set to 'true' marshall and immediately unmarshall before handing it on
15
15
  # messages
16
16
  def self.marshall_messages=(flag)
17
17
  @@marshall_messages = (flag == true)
18
18
  end
19
-
19
+
20
20
  # def self.address_for(name)
21
21
  # "#{name}@local"
22
22
  # end
23
-
23
+
24
24
  def to_s
25
- "Mock::Topic<#{id}>"
25
+ "Local::Topic<#{id}>"
26
26
  end
27
-
27
+
28
28
  def address
29
- "local:/#{id}"
29
+ @id
30
30
  end
31
-
31
+
32
32
  def on_subscribed(&block)
33
33
  return unless block
34
-
34
+
35
35
  OmfCommon.eventloop.after(0) do
36
36
  block.arity == 1 ? block.call(self) : block.call
37
37
  end
38
- end
39
-
38
+ end
39
+
40
40
  private
41
-
41
+
42
42
  def _send_message(msg, block = nil)
43
43
  super
44
44
  debug "(#{id}) Send message #{msg.inspect}"
@@ -47,7 +47,7 @@ module OmfCommon
47
47
  Message.parse(payload, content_type) do
48
48
  OmfCommon.eventloop.after(0) do
49
49
  on_incoming_message(msg)
50
- end
50
+ end
51
51
  end
52
52
  else
53
53
  OmfCommon.eventloop.after(0) do
@@ -55,9 +55,9 @@ module OmfCommon
55
55
  end
56
56
  end
57
57
  end
58
-
58
+
59
59
 
60
60
  end # class
61
- end # module
61
+ end # module
62
62
  end # module
63
63
  end # module
@@ -57,6 +57,10 @@ class Comm
57
57
  { proto: :xmpp, user: jid.node, domain: jid.domain }
58
58
  end
59
59
 
60
+ def string_to_address(a_string)
61
+ "xmpp://#{a_string}@#{jid.domain}"
62
+ end
63
+
60
64
  # Capture system :INT & :TERM signal
61
65
  def on_interrupted(&block)
62
66
  @cbks[:interpreted] << block
@@ -100,6 +104,7 @@ class Comm
100
104
  info "Reconnected"
101
105
  else
102
106
  info "Connected"
107
+ OmfCommon::DSL::Xmpp::MPConnection.inject(Time.now.to_f, jid, 'connected') if OmfCommon::Measure.enabled?
103
108
  @cbks[:connected].each { |cbk| cbk.call(self) }
104
109
  # It will be reconnection after this
105
110
  @lock.synchronize do
@@ -196,7 +201,7 @@ class Comm
196
201
  def subscribe(topic, opts = {}, &block)
197
202
  topic = topic.first if topic.is_a? Array
198
203
  OmfCommon::Comm::XMPP::Topic.create(topic, &block)
199
- MPSubscription.inject(Time.now.to_f, jid, 'join', topic) if OmfCommon::Measure.enabled?
204
+ OmfCommon::DSL::Xmpp::MPSubscription.inject(Time.now.to_f, jid, 'join', topic) if OmfCommon::Measure.enabled?
200
205
  end
201
206
 
202
207
  def _subscribe(topic, pubsub_host = default_host, &block)
@@ -212,7 +217,7 @@ class Comm
212
217
  pubsub.subscriptions(pubsub_host) do |m|
213
218
  m[:subscribed] && m[:subscribed].each do |s|
214
219
  pubsub.unsubscribe(s[:node], nil, s[:subid], pubsub_host, &callback_logging(__method__, s[:node], s[:subid]))
215
- MPSubscription.inject(Time.now.to_f, jid, 'leave', s[:node]) if OmfCommon::Measure.enabled?
220
+ OmfCommon::DSL::Xmpp::MPSubscription.inject(Time.now.to_f, jid, 'leave', s[:node]) if OmfCommon::Measure.enabled?
216
221
  end
217
222
  end
218
223
  end
@@ -240,7 +245,7 @@ class Comm
240
245
  end
241
246
 
242
247
  pubsub.publish(topic, message, pubsub_host, &callback_logging(__method__, topic, &new_block))
243
- MPPublished.inject(Time.now.to_f, jid, topic, message.to_s.gsub("\n",'')) if OmfCommon::Measure.enabled?
248
+ OmfCommon::DSL::Xmpp::MPPublished.inject(Time.now.to_f, jid, topic, message.to_s[/mid="(.{36})/, 1]) if OmfCommon::Measure.enabled?
244
249
  end
245
250
 
246
251
  # Event callback for pubsub topic event(item published)
@@ -250,15 +255,18 @@ class Comm
250
255
  passed = !event.delayed? && event.items? && !event.items.first.payload.nil? #&&
251
256
  #!published_messages.include?(OpenSSL::Digest::SHA1.new(event.items.first.payload))
252
257
 
253
- MPReceived.inject(Time.now.to_f, jid, event.node, event.items.first.payload.to_s.gsub("\n",'')) if OmfCommon::Measure.enabled? && passed
254
-
255
258
  if additional_guard
256
259
  passed && additional_guard.call(event)
257
260
  else
258
261
  passed
259
262
  end
260
263
  end
261
- pubsub_event(guard_block, &callback_logging(__method__, &block))
264
+
265
+ mblock = proc do |stanza|
266
+ OmfCommon::DSL::Xmpp::MPReceived.inject(Time.now.to_f, jid, stanza.node, stanza.to_s[/mid="(.{36})/, 1]) if OmfCommon::Measure.enabled?
267
+ block.call(stanza) if block
268
+ end
269
+ pubsub_event(guard_block, &callback_logging(__method__, &mblock))
262
270
  end
263
271
 
264
272
  private
@@ -20,7 +20,7 @@ module OmfCommon
20
20
  param :time, :type => :double
21
21
  param :jid, :type => :string
22
22
  param :topic, :type => :string
23
- param :xml_stanza, :type => :string
23
+ param :mid, :type => :string
24
24
  end
25
25
 
26
26
  class MPReceived < OML4R::MPBase
@@ -28,7 +28,7 @@ module OmfCommon
28
28
  param :time, :type => :double
29
29
  param :jid, :type => :string
30
30
  param :topic, :type => :string
31
- param :xml_stanza, :type => :string
31
+ param :mid, :type => :string
32
32
  end
33
33
 
34
34
  class MPSubscription < OML4R::MPBase