1
# -*- coding: utf-8 -*-
3
# Author: Alejandro J. Cura <alecu@canonical.com>
5
# Copyright 2010 Canonical Ltd.
7
# This program is free software: you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License version 3, as published
9
# by the Free Software Foundation.
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
# PURPOSE. See the GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License along
17
# with this program. If not, see <http://www.gnu.org/licenses/>.
18
"""Tests for txkeyring."""
24
from twisted.internet.defer import inlineCallbacks, returnValue
25
from ubuntuone.devtools.testcase import DBusTestCase
27
from ubuntu_sso.utils import txsecrets
29
# DBus exported objects have different naming conventions than vanilla python
30
# pylint: disable=C0103
32
# Dear pylint, my clock says... 2010. Do you need a tutorial on decorators?
33
# pylint: disable=C0322
35
# pylint complains when things are a little too dynamic
36
# pylint: disable=E1101
39
class SampleMiscException(Exception):
40
"""An exception that will be turned into a DBus Exception."""
43
class ItemMock(dbus.service.Object):
44
"""An item contains a secret, lookup attributes and has a label."""
45
get_secret_fail = False
50
def __init__(self, collection, label, attributes, value, *args, **kwargs):
51
"""Initialize this instance."""
52
super(ItemMock, self).__init__(*args, **kwargs)
53
self.collection = collection
55
self.attributes = attributes
58
@dbus.service.method(dbus_interface=txsecrets.ITEM_IFACE,
61
"""Delete this item."""
63
raise SampleMiscException()
64
self.collection.items.remove(self)
65
if self.delete_prompt:
66
prompt_path = create_object_path("/org/freedesktop/secrets/prompt")
67
prompt = self.dbus_publish(prompt_path, PromptMock,
69
dismissed=self.dismissed)
74
@dbus.service.method(dbus_interface=txsecrets.ITEM_IFACE,
75
in_signature="o", out_signature="(oayay)")
76
def GetSecret(self, session):
77
"""Retrieve the secret for this item."""
78
if self.get_secret_fail:
79
raise SampleMiscException()
80
return (session, "", self.value)
82
def matches(self, search_attr):
83
"""See if this item matches a given search."""
84
for k, val in search_attr.items():
85
if k not in self.attributes:
87
if self.attributes[k] != val:
92
class PromptMock(dbus.service.Object):
93
"""A prompt necessary to complete an operation."""
95
def __init__(self, dismissed=True,
96
result=dbus.String("", variant_level=1), *args, **kwargs):
97
"""Initialize this instance."""
98
super(PromptMock, self).__init__(*args, **kwargs)
99
self.dismissed = dismissed
102
@dbus.service.method(dbus_interface=txsecrets.PROMPT_IFACE,
104
def Prompt(self, window_id):
105
"""Perform the prompt."""
106
self.Completed(self.dismissed, self.result)
108
@dbus.service.signal(dbus_interface=txsecrets.PROMPT_IFACE,
110
def Completed(self, dismissed, result):
111
"""The prompt and operation completed."""
114
class CollectionMock(dbus.service.Object):
115
"""A collection of items containing secrets."""
116
SUPPORTS_MULTIPLE_OBJECT_PATHS = True
117
SUPPORTS_MULTIPLE_CONNECTIONS = True
118
create_item_prompt = False
120
create_item_fail = False
122
unlock_prompts = False
124
def __init__(self, label, *args, **kwargs):
125
"""Initialize this instance."""
126
super(CollectionMock, self).__init__(*args, **kwargs)
130
@dbus.service.method(dbus_interface=txsecrets.COLLECTION_IFACE,
131
in_signature="a{sv}(oayay)b", out_signature="oo",
133
def CreateItem(self, properties, secret, replace):
134
"""Create an item with the given attributes, secret and label.
136
If replace is set, then it replaces an item already present with the
137
same values for the attributes.
139
if self.create_item_fail:
140
raise SampleMiscException()
141
attributes = properties[txsecrets.ATTRIBUTES_PROPERTY]
142
item_label = properties[txsecrets.LABEL_PROPERTY]
143
session, parameters, value = secret
144
item_path = create_object_path("/org/freedesktop/secrets/collection/" +
146
item = self.dbus_publish(item_path, ItemMock, self, item_label,
148
self.items.append(item)
149
if self.create_item_prompt:
150
prompt_path = create_object_path("/org/freedesktop/secrets/prompt")
151
prompt = self.dbus_publish(prompt_path, PromptMock,
153
dismissed=self.dismissed)
159
class SessionMock(dbus.service.Object):
160
"""A session tracks state between the service and a client application."""
162
@dbus.service.method(dbus_interface=txsecrets.SESSION_IFACE)
164
"""Close this session."""
167
class SecretServiceMock(dbus.service.Object):
168
"""The Secret Service manages all the sessions and collections."""
169
create_collection_prompt = False
170
create_collection_fail = False
171
open_session_fail = False
174
def __init__(self, *args, **kwargs):
175
"""Initialize this instance."""
176
super(SecretServiceMock, self).__init__(*args, **kwargs)
178
self.collections = {}
180
@dbus.service.method(dbus_interface=txsecrets.SERVICE_IFACE,
181
in_signature="sv", out_signature="vo")
182
def OpenSession(self, algorithm, algorithm_parameters):
183
"""Open a unique session for the caller application."""
184
if self.open_session_fail:
185
raise SampleMiscException()
186
session_path = create_object_path("/org/freedesktop/secrets/session")
187
session = self.dbus_publish(session_path, SessionMock)
188
self.sessions[session_path] = session
191
def make_coll_path(self, label):
192
"""Make the path to a collection with its label"""
193
return "/org/freedesktop/secrets/collection/" + label
195
@dbus.service.method(dbus_interface=txsecrets.SERVICE_IFACE,
196
in_signature="a{sv}", out_signature="oo")
197
def CreateCollection(self, properties):
198
"""Create a new collection with the specified properties."""
199
if self.create_collection_fail:
200
raise SampleMiscException()
201
label = str(properties[txsecrets.LABEL_PROPERTY])
202
if len(self.collections):
203
coll_path = self.make_coll_path(label)
205
coll_path = txsecrets.DEFAULT_COLLECTION
206
collection = self.dbus_publish(coll_path, CollectionMock, label)
207
self.collections[label] = collection
209
if self.create_collection_prompt:
210
prompt_path = create_object_path("/org/freedesktop/secrets/prompt")
211
prompt = self.dbus_publish(prompt_path, PromptMock,
213
dismissed=self.dismissed)
216
return collection, "/"
218
@dbus.service.method(dbus_interface=txsecrets.SERVICE_IFACE,
219
in_signature="a{ss}", out_signature="aoao")
220
def SearchItems(self, attributes):
221
"""Find items in any collection."""
224
for c in self.collections.values():
226
append_item = locked_items.append
228
append_item = unlocked_items.append
230
if i.matches(attributes):
233
return unlocked_items, locked_items
235
def unlock_objects(self, objects):
236
"""Unlock the objects or its containers."""
237
for c in self.collections.values():
238
if c.__dbus_object_path__ in objects:
241
if i.__dbus_object_path__ in objects:
244
@dbus.service.method(dbus_interface=txsecrets.SERVICE_IFACE,
245
in_signature="ao", out_signature="aoo")
246
def Unlock(self, objects):
247
"""Unlock the specified objects."""
250
for c in self.collections.values():
252
path = i.__dbus_object_path__
257
unlocked.append(path)
259
prompt_path = create_object_path("/org/freedesktop/secrets/prompt")
260
self.unlock_objects(objects)
261
prompt = self.dbus_publish(prompt_path, PromptMock,
263
dismissed=self.dismissed)
264
return unlocked, prompt
266
self.unlock_objects(objects)
269
@dbus.service.method(dbus_interface="org.freedesktop.DBus.Properties",
270
in_signature="ss", out_signature="v")
271
def Get(self, interface, propname):
272
"""The only property implemented is Collections."""
273
if interface == txsecrets.SERVICE_IFACE and propname == "Collections":
274
coll_paths = [self.make_coll_path(l) for l in self.collections]
275
return dbus.Array(coll_paths, signature="o", variant_level=1)
278
def create_object_path(base):
279
"""Create a random object path given a base path."""
280
random = uuid.uuid4().hex
281
return base + "/" + random
284
class BaseTestCase(DBusTestCase):
285
"""Base class for DBus tests."""
289
super(BaseTestCase, self).setUp()
290
self.session_bus = dbus.SessionBus()
291
self.mock_service = self.dbus_publish(txsecrets.SECRETS_SERVICE,
293
self.secretservice = txsecrets.SecretService()
295
def dbus_publish(self, object_path, object_class, *args, **kwargs):
296
"""Create an object and publish it on the bus."""
297
name = self.session_bus.request_name(txsecrets.BUS_NAME,
298
dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
299
self.assertNotEqual(name, dbus.bus.REQUEST_NAME_REPLY_EXISTS)
300
mock_object = object_class(*args, object_path=object_path,
301
conn=self.session_bus, **kwargs)
302
self.addCleanup(mock_object.remove_from_connection)
303
mock_object.dbus_publish = self.dbus_publish
307
def create_sample_collection(self, label):
308
"""Create a collection with a given label."""
309
coll = yield self.secretservice.create_collection(label)
313
class SecretServiceTestCase(BaseTestCase):
314
"""Test the Secret Service class."""
317
def test_open_session(self):
318
"""The secret service session is opened."""
319
result = yield self.secretservice.open_session()
320
self.assertEqual(result, self.secretservice)
323
def test_open_session_throws_dbus_error_as_failure(self):
324
"""The secret service open session throws a dbus error as a failure."""
325
d = self.secretservice.open_session()
326
self.mock_service.open_session_fail = True
327
yield self.assertFailure(d, dbus.exceptions.DBusException)
330
def test_open_session_fails_before_opening_as_failure(self):
331
"""A dbus error before opening the session is thrown as a failure."""
333
def fail(*args, **kwargs):
334
"""Throw a DBus exception."""
335
raise dbus.exceptions.DBusException()
337
self.patch(txsecrets.dbus, "SessionBus", fail)
338
d = self.secretservice.open_session()
339
self.mock_service.open_session_fail = True
340
yield self.assertFailure(d, dbus.exceptions.DBusException)
343
def test_create_collection(self):
344
"""The secret service creates a collection."""
345
yield self.secretservice.open_session()
346
collection_label = "sample_keyring"
347
yield self.create_sample_collection(collection_label)
348
self.assertIn(collection_label, self.mock_service.collections)
351
def test_create_collection_prompt(self):
352
"""The secret service creates a collection after a prompt."""
353
yield self.secretservice.open_session()
354
self.mock_service.create_collection_prompt = True
355
collection_label = "sample_keyring"
356
yield self.create_sample_collection(collection_label)
357
self.assertIn(collection_label, self.mock_service.collections)
360
def test_create_collection_prompt_dismissed(self):
361
"""The service fails to create collection when prompt dismissed."""
362
yield self.secretservice.open_session()
363
self.mock_service.create_collection_prompt = True
364
self.mock_service.dismissed = True
365
collection_label = "sample_keyring"
366
yield self.assertFailure(
367
self.create_sample_collection(collection_label),
368
txsecrets.UserCancelled)
371
def test_create_collection_throws_dbus_error(self):
372
"""The service fails to create collection on a DBus error."""
373
yield self.secretservice.open_session()
374
self.mock_service.create_collection_fail = True
375
collection_label = "sample_keyring"
376
yield self.assertFailure(
377
self.create_sample_collection(collection_label),
378
dbus.exceptions.DBusException)
381
def test_prompt_accepted(self):
382
"""A prompt is accepted."""
383
yield self.secretservice.open_session()
384
expected_result = "hello world"
385
prompt_path = "/prompt"
386
self.dbus_publish(prompt_path, PromptMock, result=expected_result,
388
result = yield self.secretservice.do_prompt(prompt_path)
389
self.assertEqual(result, expected_result)
392
def test_prompt_dismissed(self):
393
"""A prompt is dismissed with a UserCancelled failure."""
394
yield self.secretservice.open_session()
395
expected_result = "hello world2"
396
prompt_path = "/prompt"
397
self.dbus_publish(prompt_path, PromptMock, result=expected_result,
399
d = self.secretservice.do_prompt(prompt_path)
400
self.assertFailure(d, txsecrets.UserCancelled)
403
def test_search_unlocked_items(self):
404
"""The secret service searchs for unlocked items."""
405
yield self.secretservice.open_session()
406
coll = yield self.create_sample_collection("sample_keyring")
407
attr = {"key-type": "Ubuntu SSO credentials"}
408
sample_secret = "secret83!"
409
yield coll.create_item("Cucaracha", attr, sample_secret)
410
items = yield self.secretservice.search_items(attr)
411
self.assertEqual(len(items), 1)
412
value = yield items[0].get_value()
413
self.assertEqual(value, sample_secret)
416
def test_search_locked_items(self):
417
"""The secret service searchs for locked items."""
418
yield self.secretservice.open_session()
419
collection_name = "sample_keyring"
420
coll = yield self.create_sample_collection(collection_name)
421
mock_collection = self.mock_service.collections[collection_name]
422
mock_collection.locked = True
423
attr = {"key-type": "Ubuntu SSO credentials"}
424
sample_secret = "secret99!"
425
yield coll.create_item("Cucaracha", attr, sample_secret)
426
items = yield self.secretservice.search_items(attr)
427
self.assertEqual(len(items), 1)
428
value = yield items[0].get_value()
429
self.assertEqual(value, sample_secret)
432
def test_search_locked_items_prompts(self):
433
"""The secret service searchs for locked items after a prompt."""
434
yield self.secretservice.open_session()
435
collection_name = "sample_keyring"
436
coll = yield self.create_sample_collection(collection_name)
437
mock_collection = self.mock_service.collections[collection_name]
438
mock_collection.locked = True
439
mock_collection.unlock_prompts = True
440
attr = {"key-type": "Ubuntu SSO credentials"}
441
sample_secret = "secret99!"
442
yield coll.create_item("Cucaracha", attr, sample_secret)
443
items = yield self.secretservice.search_items(attr)
444
self.assertEqual(len(items), 1)
445
value = yield items[0].get_value()
446
self.assertEqual(value, sample_secret)
449
def test_search_locked_items_prompts_dismissed(self):
450
"""Service fails search for locked items after dismissed prompt."""
451
yield self.secretservice.open_session()
452
collection_name = "sample_keyring"
453
coll = yield self.create_sample_collection(collection_name)
454
mock_collection = self.mock_service.collections[collection_name]
455
mock_collection.locked = True
456
mock_collection.unlock_prompts = True
457
self.mock_service.dismissed = True
458
attr = {"key-type": "Ubuntu SSO credentials"}
459
sample_secret = "secret99!"
460
yield coll.create_item("Cucaracha", attr, sample_secret)
461
d = self.secretservice.search_items(attr)
462
yield self.assertFailure(d, txsecrets.UserCancelled)
465
def test_search_items_merges_unlocked_and_locked_items(self):
466
"""search_items merges unlocked and locked items."""
467
yield self.secretservice.open_session()
468
attr = {"key-type": "Ubuntu SSO credentials"}
470
collection_name = "coll1"
471
coll = yield self.create_sample_collection(collection_name)
472
mock_coll1 = self.mock_service.collections[collection_name]
473
mock_coll1.locked = False
474
mock_coll1.unlock_prompts = False
475
unlocked_secret = "coll 1 secret!"
476
yield coll.create_item("Cucaracha", attr, unlocked_secret)
478
collection_name = "coll2"
479
coll = yield self.create_sample_collection(collection_name)
480
mock_coll2 = self.mock_service.collections[collection_name]
481
mock_coll2.locked = True
482
mock_coll2.unlock_prompts = False
483
locked_secret = "coll 2 secret!"
484
yield coll.create_item("Cucaracha", attr, locked_secret)
486
result = yield self.secretservice.search_items(attr)
487
self.assertEqual(len(result), 2)
490
def test_search_items_merges_unlocked_locked_and_prompt_items(self):
491
"""search_items merges unlocked, locked and prompt items."""
492
yield self.secretservice.open_session()
493
attr = {"key-type": "Ubuntu SSO credentials"}
495
collection_name = "coll1"
496
coll = yield self.create_sample_collection(collection_name)
497
mock_coll1 = self.mock_service.collections[collection_name]
498
mock_coll1.locked = False
499
mock_coll1.unlock_prompts = False
500
unlocked_secret = "coll 1 secret!"
501
yield coll.create_item("Cucaracha", attr, unlocked_secret)
503
collection_name = "coll2"
504
coll = yield self.create_sample_collection(collection_name)
505
mock_coll2 = self.mock_service.collections[collection_name]
506
mock_coll2.locked = True
507
mock_coll2.unlock_prompts = False
508
locked_secret = "coll 2 secret!"
509
yield coll.create_item("Cucaracha", attr, locked_secret)
511
collection_name = "coll3"
512
coll = yield self.create_sample_collection(collection_name)
513
mock_coll3 = self.mock_service.collections[collection_name]
514
mock_coll3.locked = True
515
mock_coll3.unlock_prompts = True
516
locked_secret = "coll 3 secret!"
517
yield coll.create_item("Cucaracha", attr, locked_secret)
519
result = yield self.secretservice.search_items(attr)
520
self.assertEqual(len(result), 3)
523
def test_get_default_collection(self):
524
"""The default collection is returned."""
525
yield self.secretservice.open_session()
526
collection_name = "sample_keyring"
527
yield self.create_sample_collection(collection_name)
528
self.assertEqual(len(self.mock_service.collections), 1)
529
yield self.secretservice.get_default_collection()
530
self.assertEqual(len(self.mock_service.collections), 1)
533
def test_get_default_collection_created_if_nonexistent(self):
534
"""The default collection is created if it doesn't exist yet."""
535
yield self.secretservice.open_session()
536
self.assertEqual(len(self.mock_service.collections), 0)
537
yield self.secretservice.get_default_collection()
538
self.assertEqual(len(self.mock_service.collections), 1)
541
class CollectionTestCase(BaseTestCase):
542
"""Test the Collection class."""
545
def test_create_item(self):
546
"""The collection creates an item."""
547
yield self.secretservice.open_session()
548
collection_label = "sample_keyring"
549
yield self.create_sample_collection(collection_label)
550
coll = yield self.secretservice.get_default_collection()
551
mock_collection = self.mock_service.collections[collection_label]
552
attr = {"key-type": "Ubuntu 242 credentials"}
553
sample_secret = "secret!"
554
yield coll.create_item("Cucaracha", attr, sample_secret)
555
self.assertEqual(len(mock_collection.items), 1)
556
self.assertEqual(mock_collection.items[0].value, sample_secret)
559
def test_create_item_prompt(self):
560
"""The collection creates an item after a prompt."""
561
yield self.secretservice.open_session()
562
collection_label = "sample_keyring"
563
yield self.create_sample_collection(collection_label)
564
coll = yield self.secretservice.get_default_collection()
565
mock_collection = self.mock_service.collections[collection_label]
566
mock_collection.create_item_prompt = True
567
attr = {"key-type": "Ubuntu 242 credentials"}
568
sample_secret = "secret2!"
569
yield coll.create_item("Cucaracha", attr, sample_secret)
570
self.assertEqual(len(mock_collection.items), 1)
571
self.assertEqual(mock_collection.items[0].value, sample_secret)
574
def test_create_item_prompt_dismissed(self):
575
"""The collection fails to create an item when prompt is dismissed."""
576
yield self.secretservice.open_session()
577
collection_label = "sample_keyring"
578
yield self.create_sample_collection(collection_label)
579
coll = yield self.secretservice.get_default_collection()
580
mock_collection = self.mock_service.collections[collection_label]
581
mock_collection.create_item_prompt = True
582
mock_collection.dismissed = True
583
attr = {"key-type": "Ubuntu 242 credentials"}
584
sample_secret = "secret3!"
585
yield self.assertFailure(coll.create_item("Cuca", attr, sample_secret),
586
txsecrets.UserCancelled)
589
def test_create_item_throws_dbus_error(self):
590
"""The collection fails to create an item when DBus fails."""
591
yield self.secretservice.open_session()
592
collection_label = "sample_keyring"
593
yield self.create_sample_collection(collection_label)
594
coll = yield self.secretservice.get_default_collection()
595
mock_collection = self.mock_service.collections[collection_label]
596
mock_collection.create_item_fail = True
597
attr = {"key-type": "Ubuntu 242 credentials"}
598
sample_secret = "secret4!"
599
yield self.assertFailure(coll.create_item("Cuca", attr, sample_secret),
600
dbus.exceptions.DBusException)
603
class ItemTestCase(BaseTestCase):
604
"""Test the Item class."""
607
def test_get_value(self):
608
"""The secret value is retrieved from the item."""
609
yield self.secretservice.open_session()
610
coll = yield self.create_sample_collection("sample_keyring")
611
attr = {"key-type": "Ubuntu SSO credentials"}
612
sample_secret = "secret83!"
613
yield coll.create_item("Cucaracha", attr, sample_secret)
614
items = yield self.secretservice.search_items(attr)
615
self.assertEqual(len(items), 1)
616
value = yield items[0].get_value()
617
self.assertEqual(value, sample_secret)
620
def test_get_value_throws_dbus_error(self):
621
"""The secret value is not retrieved if DBus fails."""
622
yield self.secretservice.open_session()
623
collection_label = "sample_keyring"
624
coll = yield self.create_sample_collection(collection_label)
625
attr = {"key-type": "Ubuntu SSO credentials"}
626
sample_secret = "secret83!"
627
yield coll.create_item("Cucaracha", attr, sample_secret)
628
items = yield self.secretservice.search_items(attr)
629
self.assertEqual(len(items), 1)
630
mock = self.mock_service.collections[collection_label].items[0]
631
mock.get_secret_fail = True
632
yield self.assertFailure(items[0].get_value(),
633
dbus.exceptions.DBusException)
636
def test_delete(self):
637
"""The item is deleted."""
638
yield self.secretservice.open_session()
639
coll = yield self.create_sample_collection("sample_keyring")
640
attr = {"key-type": "Ubuntu SSO credentials"}
641
sample_secret = "secret83!"
642
yield coll.create_item("Cucaracha", attr, sample_secret)
643
items = yield self.secretservice.search_items(attr)
644
self.assertEqual(len(items), 1)
645
yield items[0].delete()
646
items = yield self.secretservice.search_items(attr)
647
self.assertEqual(len(items), 0)
650
def test_delete_prompt(self):
651
"""The item is deleted after a prompt."""
652
yield self.secretservice.open_session()
653
collection_label = "sample_keyring"
654
coll = yield self.create_sample_collection(collection_label)
655
attr = {"key-type": "Ubuntu SSO credentials"}
656
sample_secret = "secret83!"
657
yield coll.create_item("Cucaracha", attr, sample_secret)
658
items = yield self.secretservice.search_items(attr)
659
self.assertEqual(len(items), 1)
660
mock_item = self.mock_service.collections[collection_label].items[0]
661
mock_item.delete_prompt = True
662
yield items[0].delete()
663
items = yield self.secretservice.search_items(attr)
664
self.assertEqual(len(items), 0)
667
def test_delete_prompt_dismissed(self):
668
"""The item is not deleted after a dismissed prompt."""
669
yield self.secretservice.open_session()
670
collection_label = "sample_keyring"
671
coll = yield self.create_sample_collection(collection_label)
672
attr = {"key-type": "Ubuntu SSO credentials"}
673
sample_secret = "secret83!"
674
yield coll.create_item("Cucaracha", attr, sample_secret)
675
items = yield self.secretservice.search_items(attr)
676
self.assertEqual(len(items), 1)
677
mock_item = self.mock_service.collections[collection_label].items[0]
678
mock_item.delete_prompt = True
679
mock_item.dismissed = True
680
yield self.assertFailure(items[0].delete(), txsecrets.UserCancelled)
683
def test_delete_throws_dbus_error(self):
684
"""The item is not deleted when a DBus error happens."""
685
yield self.secretservice.open_session()
686
collection_label = "sample_keyring"
687
coll = yield self.create_sample_collection(collection_label)
688
attr = {"key-type": "Ubuntu SSO credentials"}
689
sample_secret = "secret83!"
690
yield coll.create_item("Cucaracha", attr, sample_secret)
691
items = yield self.secretservice.search_items(attr)
692
self.assertEqual(len(items), 1)
693
mock_item = self.mock_service.collections[collection_label].items[0]
694
mock_item.delete_fail = True
695
yield self.assertFailure(items[0].delete(),
696
dbus.exceptions.DBusException)