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.
- 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
|