omf_common 6.0.7.1 → 6.0.8.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +14 -6
- data/bin/omf_cert.rb +175 -0
- data/example/auth_test.rb +76 -0
- data/lib/omf_common.rb +12 -1
- data/lib/omf_common/auth/certificate.rb +255 -72
- data/lib/omf_common/auth/certificate_store.rb +39 -15
- data/lib/omf_common/auth/jwt_authenticator.rb +69 -0
- data/lib/omf_common/auth/pdp/test_pdp.rb +21 -0
- data/lib/omf_common/comm.rb +8 -1
- data/lib/omf_common/comm/amqp/amqp_communicator.rb +7 -1
- data/lib/omf_common/comm/amqp/amqp_mp.rb +29 -0
- data/lib/omf_common/comm/amqp/amqp_topic.rb +4 -2
- data/lib/omf_common/comm/local/local_topic.rb +14 -14
- data/lib/omf_common/comm/xmpp/communicator.rb +14 -6
- data/lib/omf_common/comm/xmpp/xmpp_mp.rb +2 -2
- data/lib/omf_common/message.rb +27 -6
- data/lib/omf_common/message/json/json_message.rb +36 -21
- data/lib/omf_common/message/xml/message.rb +27 -21
- data/lib/omf_common/version.rb +8 -1
- data/omf_common.gemspec +4 -3
- data/test/fixture/alice-cert.pem +26 -0
- data/test/fixture/alice-key.pem +15 -0
- data/test/omf_common/auth/certificate_spec.rb +20 -41
- data/test/omf_common/auth/certificate_store_spec.rb +19 -21
- data/test/omf_common/comm/xmpp/communicator_spec.rb +4 -1
- data/test/omf_common/message/xml/message_spec.rb +2 -2
- metadata +66 -39
@@ -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
|
33
|
+
def register_trusted(certificate)
|
33
34
|
@@instance.synchronize do
|
34
35
|
begin
|
35
|
-
@x509_store.add_cert(certificate.to_x509)
|
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
|
-
|
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
|
-
|
48
|
+
def register(certificate)
|
49
|
+
raise "Expected Certificate, but got '#{certificate.class}'" unless certificate.is_a? Certificate
|
45
50
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
57
|
-
if (cert = Certificate.
|
63
|
+
def register_x509(cert_pem)
|
64
|
+
if (cert = Certificate.create_from_pem(cert_pem))
|
58
65
|
debug "REGISTERED #{cert}"
|
59
|
-
register(cert
|
66
|
+
register(cert)
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
63
70
|
def cert_for(url)
|
64
|
-
@certs
|
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
|
-
|
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
|
data/lib/omf_common/comm.rb
CHANGED
@@ -67,7 +67,7 @@ module OmfCommon
|
|
67
67
|
end
|
68
68
|
@@instance = inst
|
69
69
|
mopts = provider[:message_provider]
|
70
|
-
mopts[:authenticate] =
|
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
|
-
|
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
|
-
"
|
25
|
+
"Local::Topic<#{id}>"
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def address
|
29
|
-
|
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.
|
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
|
-
|
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 :
|
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 :
|
31
|
+
param :mid, :type => :string
|
32
32
|
end
|
33
33
|
|
34
34
|
class MPSubscription < OML4R::MPBase
|