ruby-dbus 0.18.1 → 0.20.0
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 +4 -4
- data/NEWS.md +26 -0
- data/VERSION +1 -1
- data/examples/simple/get_id.rb +4 -2
- data/lib/dbus/auth.rb +305 -218
- data/lib/dbus/bus.rb +57 -15
- data/lib/dbus/introspect.rb +2 -1
- data/lib/dbus/marshall.rb +1 -1
- data/lib/dbus/message_queue.rb +19 -12
- data/lib/dbus/object.rb +31 -3
- data/lib/dbus/object_manager.rb +58 -0
- data/lib/dbus.rb +1 -0
- data/spec/auth_spec.rb +225 -0
- data/spec/node_spec.rb +46 -0
- data/spec/object_manager_spec.rb +33 -0
- data/spec/object_spec.rb +10 -0
- data/spec/proxy_object_interface_spec.rb +35 -0
- data/spec/server_robustness_spec.rb +18 -6
- data/spec/service_newapi.rb +34 -0
- data/spec/service_spec.rb +18 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/thread_safety_spec.rb +55 -12
- data/spec/tools/dbus-limited-session.conf +15 -0
- metadata +8 -4
data/lib/dbus/bus.rb
CHANGED
@@ -76,6 +76,7 @@ module DBus
|
|
76
76
|
def export(obj)
|
77
77
|
obj.service = self
|
78
78
|
get_node(obj.path, create: true).object = obj
|
79
|
+
object_manager_for(obj)&.object_added(obj)
|
79
80
|
end
|
80
81
|
|
81
82
|
# Undo exporting an object _obj_.
|
@@ -93,6 +94,7 @@ module DBus
|
|
93
94
|
parent_node = get_node(parent_path, create: false)
|
94
95
|
return false unless parent_node
|
95
96
|
|
97
|
+
object_manager_for(obj)&.object_removed(obj)
|
96
98
|
obj.service = nil
|
97
99
|
parent_node.delete(node_name).object
|
98
100
|
end
|
@@ -112,18 +114,51 @@ module DBus
|
|
112
114
|
end
|
113
115
|
n = n[elem]
|
114
116
|
end
|
115
|
-
if n.nil?
|
116
|
-
DBus.logger.debug "Warning, unknown object #{path}"
|
117
|
-
end
|
118
117
|
n
|
119
118
|
end
|
120
119
|
|
120
|
+
# Find the (closest) parent of *object*
|
121
|
+
# implementing the ObjectManager interface, or nil
|
122
|
+
# @return [DBus::Object,nil]
|
123
|
+
def object_manager_for(object)
|
124
|
+
path = object.path
|
125
|
+
node_chain = get_node_chain(path)
|
126
|
+
om_node = node_chain.reverse_each.find do |node|
|
127
|
+
node.object&.is_a? DBus::ObjectManager
|
128
|
+
end
|
129
|
+
om_node&.object
|
130
|
+
end
|
131
|
+
|
132
|
+
# All objects (not paths) under this path (except itself).
|
133
|
+
# @param path [ObjectPath]
|
134
|
+
# @return [Array<DBus::Object>]
|
135
|
+
# @raise ArgumentError if the *path* does not exist
|
136
|
+
def descendants_for(path)
|
137
|
+
node = get_node(path, create: false)
|
138
|
+
raise ArgumentError, "Object path #{path} doesn't exist" if node.nil?
|
139
|
+
|
140
|
+
node.descendant_objects
|
141
|
+
end
|
142
|
+
|
121
143
|
#########
|
122
144
|
|
123
145
|
private
|
124
146
|
|
125
147
|
#########
|
126
148
|
|
149
|
+
# @raise ArgumentError if the *path* does not exist
|
150
|
+
def get_node_chain(path)
|
151
|
+
n = @root
|
152
|
+
result = [n]
|
153
|
+
path.sub(%r{^/}, "").split("/").each do |elem|
|
154
|
+
n = n[elem]
|
155
|
+
raise ArgumentError, "Object path #{path} doesn't exist" if n.nil?
|
156
|
+
|
157
|
+
result.push(n)
|
158
|
+
end
|
159
|
+
result
|
160
|
+
end
|
161
|
+
|
127
162
|
# Perform a recursive retrospection on the given current _node_
|
128
163
|
# on the given _path_.
|
129
164
|
def rec_introspect(node, path)
|
@@ -198,6 +233,15 @@ module DBus
|
|
198
233
|
.join(",")
|
199
234
|
"#{s}{#{contents_sub_inspect}}"
|
200
235
|
end
|
236
|
+
|
237
|
+
# All objects (not paths) under this path (except itself).
|
238
|
+
# @return [Array<DBus::Object>]
|
239
|
+
def descendant_objects
|
240
|
+
children_objects = values.map(&:object).compact
|
241
|
+
descendants = values.map(&:descendant_objects)
|
242
|
+
flat_descendants = descendants.reduce([], &:+)
|
243
|
+
children_objects + flat_descendants
|
244
|
+
end
|
201
245
|
end
|
202
246
|
|
203
247
|
# FIXME: rename Connection to Bus?
|
@@ -233,7 +277,6 @@ module DBus
|
|
233
277
|
@method_call_msgs = {}
|
234
278
|
@signal_matchrules = {}
|
235
279
|
@proxy = nil
|
236
|
-
@object_root = Node.new("/")
|
237
280
|
end
|
238
281
|
|
239
282
|
# Dispatch all messages that are available in the queue,
|
@@ -561,23 +604,22 @@ module DBus
|
|
561
604
|
DBus.logger.debug "Got method call on /org/freedesktop/DBus"
|
562
605
|
end
|
563
606
|
node = @service.get_node(msg.path, create: false)
|
564
|
-
if
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
# handle introspectable as an exception:
|
569
|
-
elsif msg.interface == "org.freedesktop.DBus.Introspectable" &&
|
570
|
-
msg.member == "Introspect"
|
607
|
+
# introspect a known path even if there is no object on it
|
608
|
+
if node &&
|
609
|
+
msg.interface == "org.freedesktop.DBus.Introspectable" &&
|
610
|
+
msg.member == "Introspect"
|
571
611
|
reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
|
572
612
|
reply.sender = @unique_name
|
573
613
|
xml = node.to_xml(msg.path)
|
574
614
|
reply.add_param(Type::STRING, xml)
|
575
615
|
@message_queue.push(reply)
|
616
|
+
# dispatch for an object
|
617
|
+
elsif node&.object
|
618
|
+
node.object.dispatch(msg)
|
576
619
|
else
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
obj&.dispatch(msg)
|
620
|
+
reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
|
621
|
+
"Object #{msg.path} doesn't exist")
|
622
|
+
@message_queue.push(reply)
|
581
623
|
end
|
582
624
|
when DBus::Message::SIGNAL
|
583
625
|
# the signal can match multiple different rules
|
data/lib/dbus/introspect.rb
CHANGED
@@ -270,7 +270,7 @@ module DBus
|
|
270
270
|
class Property
|
271
271
|
# @return [Symbol] The name of the property, for example FooBar.
|
272
272
|
attr_reader :name
|
273
|
-
# @return [
|
273
|
+
# @return [Type]
|
274
274
|
attr_reader :type
|
275
275
|
# @return [Symbol] :read :write or :readwrite
|
276
276
|
attr_reader :access
|
@@ -282,6 +282,7 @@ module DBus
|
|
282
282
|
|
283
283
|
def initialize(name, type, access, ruby_name:)
|
284
284
|
@name = name.to_sym
|
285
|
+
type = DBus.type(type) unless type.is_a?(Type)
|
285
286
|
@type = type
|
286
287
|
@access = access
|
287
288
|
@ruby_name = ruby_name
|
data/lib/dbus/marshall.rb
CHANGED
@@ -327,7 +327,7 @@ module DBus
|
|
327
327
|
# Damn ruby rocks here
|
328
328
|
val = val.to_a
|
329
329
|
end
|
330
|
-
# If string is
|
330
|
+
# If string is received and ay is expected, explode the string
|
331
331
|
if val.is_a?(String) && child_type.sigtype == Type::BYTE
|
332
332
|
val = val.bytes
|
333
333
|
end
|
data/lib/dbus/message_queue.rb
CHANGED
@@ -19,9 +19,11 @@ module DBus
|
|
19
19
|
attr_reader :socket
|
20
20
|
|
21
21
|
def initialize(address)
|
22
|
+
DBus.logger.debug "MessageQueue: #{address}"
|
22
23
|
@address = address
|
23
24
|
@buffer = ""
|
24
25
|
@is_tcp = false
|
26
|
+
@mutex = Mutex.new
|
25
27
|
connect
|
26
28
|
end
|
27
29
|
|
@@ -32,23 +34,28 @@ module DBus
|
|
32
34
|
# @raise EOFError
|
33
35
|
# @todo failure modes
|
34
36
|
def pop(blocking: true)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
# FIXME: this is not enough, the R/W test deadlocks on shared connections
|
38
|
+
@mutex.synchronize do
|
39
|
+
buffer_from_socket_nonblock
|
40
|
+
message = message_from_buffer_nonblock
|
41
|
+
if blocking
|
42
|
+
# we can block
|
43
|
+
while message.nil?
|
44
|
+
r, _d, _d = IO.select([@socket])
|
45
|
+
if r && r[0] == @socket
|
46
|
+
buffer_from_socket_nonblock
|
47
|
+
message = message_from_buffer_nonblock
|
48
|
+
end
|
44
49
|
end
|
45
50
|
end
|
51
|
+
message
|
46
52
|
end
|
47
|
-
message
|
48
53
|
end
|
49
54
|
|
50
55
|
def push(message)
|
51
|
-
@
|
56
|
+
@mutex.synchronize do
|
57
|
+
@socket.write(message.marshall)
|
58
|
+
end
|
52
59
|
end
|
53
60
|
alias << push
|
54
61
|
|
@@ -129,7 +136,7 @@ module DBus
|
|
129
136
|
|
130
137
|
# Initialize the connection to the bus.
|
131
138
|
def init_connection
|
132
|
-
client = Client.new(@socket)
|
139
|
+
client = Authentication::Client.new(@socket)
|
133
140
|
client.authenticate
|
134
141
|
end
|
135
142
|
|
data/lib/dbus/object.rb
CHANGED
@@ -89,8 +89,11 @@ module DBus
|
|
89
89
|
# while the superclass keeps the old one.
|
90
90
|
self.intfs = intfs.merge(name => @@cur_intf)
|
91
91
|
end
|
92
|
-
|
93
|
-
|
92
|
+
begin
|
93
|
+
yield
|
94
|
+
ensure
|
95
|
+
@@cur_intf = nil
|
96
|
+
end
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
@@ -122,7 +125,8 @@ module DBus
|
|
122
125
|
#
|
123
126
|
# @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
|
124
127
|
# use dbus_name to override
|
125
|
-
# @param type
|
128
|
+
# @param type [Type,SingleCompleteType]
|
129
|
+
# a signature like "s" or "a(uus)" or Type::STRING
|
126
130
|
# @param dbus_name [String] if not given it is made
|
127
131
|
# by CamelCasing the ruby_name. foo_bar becomes FooBar
|
128
132
|
# to convert the Ruby convention to the DBus convention.
|
@@ -387,6 +391,23 @@ module DBus
|
|
387
391
|
property
|
388
392
|
end
|
389
393
|
|
394
|
+
# Generates information about interfaces and properties of the object
|
395
|
+
#
|
396
|
+
# Returns a hash containing interfaces names as keys. Each value is the
|
397
|
+
# same hash that would be returned by the
|
398
|
+
# org.freedesktop.DBus.Properties.GetAll() method for that combination of
|
399
|
+
# object path and interface. If an interface has no properties, the empty
|
400
|
+
# hash is returned.
|
401
|
+
#
|
402
|
+
# @return [Hash{String => Hash{String => Data::Base}}] interface -> property -> value
|
403
|
+
def interfaces_and_properties
|
404
|
+
get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll)
|
405
|
+
|
406
|
+
intfs.keys.each_with_object({}) do |interface, hash|
|
407
|
+
hash[interface] = public_send(get_all_method, interface).first
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
390
411
|
####################################################################
|
391
412
|
|
392
413
|
# use the above defined methods to declare the property-handling
|
@@ -459,5 +480,12 @@ module DBus
|
|
459
480
|
|
460
481
|
dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
|
461
482
|
end
|
483
|
+
|
484
|
+
dbus_interface "org.freedesktop.DBus.Introspectable" do
|
485
|
+
dbus_method :Introspect, "out xml_data:s" do
|
486
|
+
# The body is not used, Connection#process handles it instead
|
487
|
+
# which is more efficient and handles paths without objects.
|
488
|
+
end
|
489
|
+
end
|
462
490
|
end
|
463
491
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of the ruby-dbus project
|
4
|
+
# Copyright (C) 2022 José Iván López González
|
5
|
+
# Copyright (C) 2022 Martin Vidner
|
6
|
+
#
|
7
|
+
# This library is free software; you can redistribute it and/or
|
8
|
+
# modify it under the terms of the GNU Lesser General Public
|
9
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
10
|
+
# See the file "COPYING" for the exact licensing terms.
|
11
|
+
|
12
|
+
module DBus
|
13
|
+
# A mixin for {DBus::Object} implementing
|
14
|
+
# {https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
|
15
|
+
# org.freedesktop.DBus.ObjectManager}.
|
16
|
+
#
|
17
|
+
# {Service#export} and {Service#unexport} will look for an ObjectManager
|
18
|
+
# parent in the path hierarchy. If found, it will emit InterfacesAdded
|
19
|
+
# or InterfacesRemoved, as appropriate.
|
20
|
+
module ObjectManager
|
21
|
+
OBJECT_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager"
|
22
|
+
|
23
|
+
# @return [Hash{ObjectPath => Hash{String => Hash{String => Data::Base}}}]
|
24
|
+
# object -> interface -> property -> value
|
25
|
+
def managed_objects
|
26
|
+
# FIXME: also fix the "service" concept
|
27
|
+
descendant_objects = @service.descendants_for(path)
|
28
|
+
descendant_objects.each_with_object({}) do |obj, hash|
|
29
|
+
hash[obj.path] = obj.interfaces_and_properties
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param object [DBus::Object]
|
34
|
+
# @return [void]
|
35
|
+
def object_added(object)
|
36
|
+
InterfacesAdded(object.path, object.interfaces_and_properties)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param object [DBus::Object]
|
40
|
+
# @return [void]
|
41
|
+
def object_removed(object)
|
42
|
+
InterfacesRemoved(object.path, object.intfs.keys)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.included(base)
|
46
|
+
base.class_eval do
|
47
|
+
dbus_interface OBJECT_MANAGER_INTERFACE do
|
48
|
+
dbus_method :GetManagedObjects, "out res:a{oa{sa{sv}}}" do
|
49
|
+
[managed_objects]
|
50
|
+
end
|
51
|
+
|
52
|
+
dbus_signal :InterfacesAdded, "object:o, interfaces_and_properties:a{sa{sv}}"
|
53
|
+
dbus_signal :InterfacesRemoved, "object:o, interfaces:as"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/dbus.rb
CHANGED
@@ -24,6 +24,7 @@ require_relative "dbus/matchrule"
|
|
24
24
|
require_relative "dbus/message"
|
25
25
|
require_relative "dbus/message_queue"
|
26
26
|
require_relative "dbus/object"
|
27
|
+
require_relative "dbus/object_manager"
|
27
28
|
require_relative "dbus/object_path"
|
28
29
|
require_relative "dbus/proxy_object"
|
29
30
|
require_relative "dbus/proxy_object_factory"
|
data/spec/auth_spec.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "spec_helper"
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe DBus::Authentication::Client do
|
8
|
+
let(:socket) { instance_double("Socket") }
|
9
|
+
let(:subject) { described_class.new(socket) }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
allow(Process).to receive(:uid).and_return(999)
|
13
|
+
allow(subject).to receive(:send_nul_byte)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#next_state" do
|
17
|
+
it "raises when I forget to handle a state" do
|
18
|
+
subject.instance_variable_set(:@state, :Denmark)
|
19
|
+
expect { subject.__send__(:next_state, []) }.to raise_error(RuntimeError, /unhandled state :Denmark/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def expect_protocol(pairs)
|
24
|
+
pairs.each do |we_say, server_says|
|
25
|
+
expect(subject).to receive(:write_line).with(we_say)
|
26
|
+
next if server_says.nil?
|
27
|
+
|
28
|
+
expect(subject).to receive(:read_line).and_return(server_says)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with ANONYMOUS" do
|
33
|
+
let(:subject) { described_class.new(socket, [DBus::Authentication::Anonymous]) }
|
34
|
+
|
35
|
+
it "authentication passes" do
|
36
|
+
expect_protocol [
|
37
|
+
["AUTH ANONYMOUS 527562792044427573\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
38
|
+
["NEGOTIATE_UNIX_FD\r\n", "ERROR not for anonymous\r\n"],
|
39
|
+
["BEGIN\r\n"]
|
40
|
+
]
|
41
|
+
|
42
|
+
expect { subject.authenticate }.to_not raise_error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with EXTERNAL" do
|
47
|
+
let(:subject) { described_class.new(socket, [DBus::Authentication::External]) }
|
48
|
+
|
49
|
+
it "authentication passes, and address_uuid is set" do
|
50
|
+
expect_protocol [
|
51
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
52
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
53
|
+
["BEGIN\r\n"]
|
54
|
+
]
|
55
|
+
|
56
|
+
expect { subject.authenticate }.to_not raise_error
|
57
|
+
expect(subject.address_uuid).to eq "ffffffffffffffffffffffffffffffff"
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the server says superfluous things before an OK" do
|
61
|
+
it "authentication passes" do
|
62
|
+
expect_protocol [
|
63
|
+
["AUTH EXTERNAL 393939\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
|
64
|
+
["ERROR\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
65
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
66
|
+
["BEGIN\r\n"]
|
67
|
+
]
|
68
|
+
|
69
|
+
expect { subject.authenticate }.to_not raise_error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when the server messes up NEGOTIATE_UNIX_FD" do
|
74
|
+
it "authentication fails orderly" do
|
75
|
+
expect_protocol [
|
76
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
77
|
+
["NEGOTIATE_UNIX_FD\r\n", "I_DONT_NEGOTIATE_WITH_TENORISTS\r\n"]
|
78
|
+
]
|
79
|
+
|
80
|
+
allow(socket).to receive(:close) # want to get rid of this
|
81
|
+
# TODO: quote the server error message?
|
82
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown server reply/)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the server replies with ERROR" do
|
87
|
+
it "authentication fails orderly" do
|
88
|
+
expect_protocol [
|
89
|
+
["AUTH EXTERNAL 393939\r\n", "ERROR something failed\r\n"],
|
90
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
91
|
+
]
|
92
|
+
|
93
|
+
allow(socket).to receive(:close) # want to get rid of this
|
94
|
+
# TODO: quote the server error message?
|
95
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with EXTERNAL without uid" do
|
101
|
+
let(:subject) do
|
102
|
+
described_class.new(socket, [DBus::Authentication::External, DBus::Authentication::ExternalWithoutUid])
|
103
|
+
end
|
104
|
+
|
105
|
+
it "authentication passes" do
|
106
|
+
expect_protocol [
|
107
|
+
["AUTH EXTERNAL 393939\r\n", "REJECTED EXTERNAL\r\n"],
|
108
|
+
# this succeeds when we connect to a privileged container,
|
109
|
+
# where outside-non-root becomes inside-root
|
110
|
+
["AUTH EXTERNAL\r\n", "DATA\r\n"],
|
111
|
+
["DATA\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
112
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
113
|
+
["BEGIN\r\n"]
|
114
|
+
]
|
115
|
+
|
116
|
+
expect { subject.authenticate }.to_not raise_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with a rejected mechanism and then EXTERNAL" do
|
121
|
+
let(:rejected_mechanism) do
|
122
|
+
double("Mechanism", name: "WIMP", call: [:MechContinue, "I expect to be rejected"])
|
123
|
+
end
|
124
|
+
|
125
|
+
let(:subject) { described_class.new(socket, [rejected_mechanism, DBus::Authentication::External]) }
|
126
|
+
|
127
|
+
it "authentication eventually passes" do
|
128
|
+
expect_protocol [
|
129
|
+
[/^AUTH WIMP .*\r\n/, "REJECTED EXTERNAL\r\n"],
|
130
|
+
["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
131
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
132
|
+
["BEGIN\r\n"]
|
133
|
+
]
|
134
|
+
|
135
|
+
expect { subject.authenticate }.to_not raise_error
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with a DATA-using mechanism" do
|
140
|
+
let(:mechanism) do
|
141
|
+
double("Mechanism", name: "CHALLENGE_ME", call: [:MechContinue, "1"])
|
142
|
+
end
|
143
|
+
|
144
|
+
# try it twice to test calling #use_next_mechanism
|
145
|
+
let(:subject) { described_class.new(socket, [mechanism, mechanism]) }
|
146
|
+
|
147
|
+
it "authentication fails orderly when the server says ERROR" do
|
148
|
+
expect_protocol [
|
149
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
150
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"],
|
151
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
152
|
+
["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
153
|
+
]
|
154
|
+
|
155
|
+
allow(socket).to receive(:close) # want to get rid of this
|
156
|
+
# TODO: quote the server error message?
|
157
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "authentication fails orderly when the server says ERROR and then changes its mind" do
|
161
|
+
expect_protocol [
|
162
|
+
["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
|
163
|
+
["CANCEL\r\n", "I_CHANGED_MY_MIND please come back\r\n"]
|
164
|
+
]
|
165
|
+
|
166
|
+
allow(socket).to receive(:close) # want to get rid of this
|
167
|
+
# TODO: quote the server error message?
|
168
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown.*MIND.*REJECTED/)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "authentication passes when the server says superfluous things before DATA" do
|
172
|
+
expect_protocol [
|
173
|
+
["AUTH CHALLENGE_ME 31\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
|
174
|
+
["ERROR\r\n", "DATA\r\n"],
|
175
|
+
["DATA 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
176
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
177
|
+
["BEGIN\r\n"]
|
178
|
+
]
|
179
|
+
|
180
|
+
expect { subject.authenticate }.to_not raise_error
|
181
|
+
end
|
182
|
+
|
183
|
+
it "authentication passes when the server decides not to need the DATA" do
|
184
|
+
expect_protocol [
|
185
|
+
["AUTH CHALLENGE_ME 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
|
186
|
+
["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
|
187
|
+
["BEGIN\r\n"]
|
188
|
+
]
|
189
|
+
|
190
|
+
expect { subject.authenticate }.to_not raise_error
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "with a mechanism returning :MechError" do
|
195
|
+
let(:fallible_mechanism) do
|
196
|
+
double(name: "FALLIBLE", call: [:MechError, "not my best day"])
|
197
|
+
end
|
198
|
+
|
199
|
+
let(:subject) { described_class.new(socket, [fallible_mechanism]) }
|
200
|
+
|
201
|
+
it "authentication fails orderly" do
|
202
|
+
expect_protocol [
|
203
|
+
["ERROR not my best day\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
|
204
|
+
]
|
205
|
+
|
206
|
+
allow(socket).to receive(:close) # want to get rid of thise
|
207
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "with a badly implemented mechanism" do
|
212
|
+
let(:buggy_mechanism) do
|
213
|
+
double(name: "buggy", call: [:smurf, nil])
|
214
|
+
end
|
215
|
+
|
216
|
+
let(:subject) { described_class.new(socket, [buggy_mechanism]) }
|
217
|
+
|
218
|
+
it "authentication fails before protoxol is exchanged" do
|
219
|
+
expect(subject).to_not receive(:write_line)
|
220
|
+
expect(subject).to_not receive(:read_line)
|
221
|
+
|
222
|
+
expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /smurf/)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
data/spec/node_spec.rb
CHANGED
@@ -20,4 +20,50 @@ describe DBus::Node do
|
|
20
20
|
expect(parent.inspect).to match(/<DBus::Node [0-9a-f]+ {child0 => {},child1 => {},child2 => {}}>/)
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
describe "#descendant_objects" do
|
25
|
+
let(:manager_path) { "/org/example/FooManager" }
|
26
|
+
let(:child_paths) do
|
27
|
+
[
|
28
|
+
# note that "/org/example/FooManager/good"
|
29
|
+
# is a path under a managed object but there is no object there
|
30
|
+
"/org/example/FooManager/good/1",
|
31
|
+
"/org/example/FooManager/good/2",
|
32
|
+
"/org/example/FooManager/good/3",
|
33
|
+
"/org/example/FooManager/bad/1",
|
34
|
+
"/org/example/FooManager/bad/2"
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:non_child_paths) do
|
39
|
+
[
|
40
|
+
"/org/example/BarManager/good/1",
|
41
|
+
"/org/example/BarManager/good/2"
|
42
|
+
]
|
43
|
+
end
|
44
|
+
|
45
|
+
context "on the bus" do
|
46
|
+
let(:bus) { DBus::ASessionBus.new }
|
47
|
+
let(:service) do
|
48
|
+
# if we used org.ruby.service it would be a name collision
|
49
|
+
# ... which would not break the test for lucky reasons
|
50
|
+
bus.request_service("org.ruby.service.scratch")
|
51
|
+
end
|
52
|
+
|
53
|
+
before do
|
54
|
+
service.export(DBus::Object.new(manager_path))
|
55
|
+
non_child_paths.each do |p|
|
56
|
+
service.export(DBus::Object.new(p))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns just the descendants of the specified objects" do
|
61
|
+
child_exported_objects = child_paths.map { |p| DBus::Object.new(p) }
|
62
|
+
child_exported_objects.each { |obj| service.export(obj) }
|
63
|
+
|
64
|
+
node = service.get_node(manager_path, create: false)
|
65
|
+
expect(node.descendant_objects).to eq child_exported_objects
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
23
69
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "spec_helper"
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe DBus::ObjectManager do
|
8
|
+
describe "GetManagedObjects" do
|
9
|
+
let(:bus) { DBus::ASessionBus.new }
|
10
|
+
let(:service) { bus["org.ruby.service"] }
|
11
|
+
let(:obj) { service["/org/ruby/MyInstance"] }
|
12
|
+
let(:parent_iface) { obj["org.ruby.TestParent"] }
|
13
|
+
let(:om_iface) { obj["org.freedesktop.DBus.ObjectManager"] }
|
14
|
+
|
15
|
+
it "returns the interfaces and properties of currently managed objects" do
|
16
|
+
c1_opath = parent_iface.New("child1")
|
17
|
+
c2_opath = parent_iface.New("child2")
|
18
|
+
|
19
|
+
parent_iface.Delete(c1_opath)
|
20
|
+
expected_gmo = {
|
21
|
+
"/org/ruby/MyInstance/child2" => {
|
22
|
+
"org.freedesktop.DBus.Introspectable" => {},
|
23
|
+
"org.freedesktop.DBus.Properties" => {},
|
24
|
+
"org.ruby.TestChild" => { "Name" => "Child2" }
|
25
|
+
}
|
26
|
+
}
|
27
|
+
expect(om_iface.GetManagedObjects).to eq(expected_gmo)
|
28
|
+
|
29
|
+
parent_iface.Delete(c2_opath)
|
30
|
+
expect(om_iface.GetManagedObjects).to eq({})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/object_spec.rb
CHANGED
@@ -61,6 +61,16 @@ describe DBus::Object do
|
|
61
61
|
end
|
62
62
|
end.to raise_error(DBus::Object::UndefinedInterface)
|
63
63
|
end
|
64
|
+
|
65
|
+
it "fails when the signature is invalid" do
|
66
|
+
expect do
|
67
|
+
ObjectTest.instance_exec do
|
68
|
+
dbus_interface "org.ruby.ServerTest" do
|
69
|
+
dbus_reader :foo2, "!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end.to raise_error(DBus::Type::SignatureException)
|
73
|
+
end
|
64
74
|
end
|
65
75
|
|
66
76
|
describe ".dbus_reader, when paired with attr_accessor" do
|