2

I'm working on a little program that uses libsecret. This program should be able to create a Secret.Service...

from gi.repository import Secret
service = Secret.Service.get_sync(Secret.ServiceFlags.LOAD_COLLECTIONS)

... get a specific Collection from that Service...

# 2 is the index of a custom collection I created, not the default one.
collection = service.get_collections()[2]

... and then list all the Items inside that Collection, this by just printing their labels.

# I'm just printing a single label here for testing, I'd need all of course.
print(collection.get_items()[0].get_label())

An important detail is that the Collecction may initially be locked, and so I need to include code that checks for that possibility, and tries to unlock the Collection.

# The unlock method returns a tuple (number_of_objs_unlocked, [list_of_objs])
collection = service.unlock_sync([collection])[1][0]

This is important because the code I currently have can do all I need when the Collection is initially unlocked. However if the Collection is initially locked, even after I unlock it, I can't get the labels from the Items inside. What I can do is disconnect() the Service, recreate the Service again, get the now unlocked Collection, and this way I am able to read the label on each Item. Another interesting detail is that, after the labels are read once, I no longer required the Service reconnection to access them. This seems quite inelegant, so I started looking for a different solution.

I realized that the Collection inherited from Gio.DBusProxy and this class caches the data from the object it accesses. So I'm assuming that is the problem for me, I'm not updating the cache. This is strange though because the documentation states that Gio.DBusProxy should be able to detect changes on the original object, but that's not happening.

Now I don't know how to update the cache on that class. I've taken a look at some seahorse(another application that uses libsecret) vala code, which I wasn't able to completely decipher, I can't code vala, but that mentioned the Object.emit() method, I'm still not sure how I could use that method to achieve my goal. From the documentation(https://lazka.github.io/pgi-docs/Secret-1/#) I found another promising method, Object.notify(), which seems to be able to send notifications of changes that would enable cache updates, but I also haven't been able to properly use it yet.

I also posted on the gnome-keyring mailing list about this...

https://mail.gnome.org/archives/gnome-keyring-list/2015-November/msg00000.html

... with no answer so far, and found a bugzilla report on gnome.org that mentions this issue...

https://bugzilla.gnome.org/show_bug.cgi?id=747359

... with no solution so far(7 months) either.

So if someone could shine some light on this problem that would be great. Otherwise some inelegant code will unfortunately find it's way into my little program.


Edit-0:

Here is some code to replicate the issue in Python3. This snippet creates a collection 'test_col', with one item 'test_item', and locks the collection. Note libsecret will prompt you for the password you want for this new collection:

#!/usr/bin/env python3

from gi import require_version
require_version('Secret', '1')
from gi.repository import Secret

# Create schema
args = ['com.idlecore.test.schema']
args += [Secret.SchemaFlags.NONE]
args += [{'service': Secret.SchemaAttributeType.STRING,
          'username': Secret.SchemaAttributeType.STRING}]
schema = Secret.Schema.new(*args)

# Create 'test_col' collection
flags = Secret.CollectionCreateFlags.COLLECTION_CREATE_NONE
collection = Secret.Collection.create_sync(None, 'test_col', None, flags, None)

# Create item 'test_item' inside collection 'test_col'
attributes = {'service': 'stackoverflow', 'username': 'xor'}
password = 'password123'
value = Secret.Value(password, len(password), 'text/plain')
flags = Secret.ItemCreateFlags.NONE
Secret.Item.create_sync(collection, schema, attributes,
                        'test_item', value, flags, None)

# Lock collection
service = collection.get_service()
service.lock_sync([collection])

Then we need to restart the gnome-keyring-daemon, you can just logout and back in or use the command line:

gnome-keyrin-daemon --replace

This will setup your keyring so we can try opening a collection that is initially locked. We can do that with this code snippet. Note that you will be prompted once again for the password you set previously:

#!/usr/bin/env python3

from gi import require_version
require_version('Secret', '1')
from gi.repository import Secret

# Get the service
service = Secret.Service.get_sync(Secret.ServiceFlags.LOAD_COLLECTIONS)

# Find the correct collection
for c in service.get_collections():
    if c.get_label() == 'test_col':
        collection = c
        break

# Unlock the collection and show the item label, note that it's empty.
collection = service.unlock_sync([collection])[1][0]
print('Item Label:', collection.get_items()[0].get_label())

# Do the same thing again, and it works.
# It's necessary to disconnect the service to clear the cache,
# Otherwise we keep getting the same empty label.
service.disconnect()

# Get the service
service = Secret.Service.get_sync(Secret.ServiceFlags.LOAD_COLLECTIONS)

# Find the correct collection
for c in service.get_collections():
    if c.get_label() == 'test_col':
        collection = c
        break

# No need to unlock again, just show the item label
print('Item Label:', collection.get_items()[0].get_label())

This code attempts to read the item label twice. One the normal way, which fails, you should see an empty string, and then using a workaround, that disconnects the service and reconnects again.

xor
  • 611
  • 6
  • 15

2 Answers2

1

I came across this question while trying to update a script I use to retrieve passwords from my desktop on my laptop, and vice versa.

The clue was in the documentation for Secret.ServiceFlags—there are two:

OPEN_SESSION = 2

establish a session for transfer of secrets while initializing the Secret.Service

LOAD_COLLECTIONS = 4

load collections while initializing the Secret.Service

I think for a Service that both loads collections and allows transfer of secrets (including item labels) from those collections, we need to use both flags.

The following code (similar to your mailing list post, but without a temporary collection set up for debugging) seems to work. It gives me the label of an item:

from gi.repository import Secret
service = Secret.Service.get_sync(Secret.ServiceFlags.OPEN_SESSION |
                                  Secret.ServiceFlags.LOAD_COLLECTIONS)
collections = service.get_collections()
unlocked_collection = service.unlock_sync([collections[0]], None)[1][0]
unlocked_collection.get_items()[0].get_label()
  • I get the same behavior as before. I'll note that this behavior only happens on the first unlock. To properly test this, I need to first restart gnome-keyring-daemon with 'gnome-keyring-daemon --replace'. – xor Aug 13 '16 at 02:24
0

I have been doing this

print(collection.get_locked())
if collection.get_locked():
  service.unlock_sync(collection)

Don't know if it is going to work though because I have never hit a case where I have something that is locked. If you have a piece of sample code where I can create a locked instance of a collection then maybe I can help

Tim Hughes
  • 3,144
  • 2
  • 24
  • 24
  • I edited my original post to include, at the bottom, a couple of python3 files that can help replicate the issue. – xor Nov 12 '15 at 18:29