forked from jaraco/keyring
-
Notifications
You must be signed in to change notification settings - Fork 0
/
libsecret.py
139 lines (127 loc) · 5.22 KB
/
libsecret.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import logging
from ..util import properties
from ..backend import KeyringBackend
from ..credentials import SimpleCredential
from ..errors import (
PasswordDeleteError,
PasswordSetError,
ExceptionRaisedContext,
KeyringLocked,
)
available = False
try:
import gi
from gi.repository import Gio
from gi.repository import GLib
gi.require_version('Secret', '1')
from gi.repository import Secret
available = True
except (AttributeError, ImportError, ValueError):
pass
log = logging.getLogger(__name__)
class Keyring(KeyringBackend):
"""libsecret Keyring"""
appid = 'Python keyring library'
if available:
schema = Secret.Schema.new(
"org.freedesktop.Secret.Generic",
Secret.SchemaFlags.NONE,
{
"application": Secret.SchemaAttributeType.STRING,
"service": Secret.SchemaAttributeType.STRING,
"username": Secret.SchemaAttributeType.STRING,
}
)
@properties.ClassProperty
@classmethod
def priority(cls):
with ExceptionRaisedContext() as exc:
Secret.__name__
if exc:
raise RuntimeError("libsecret required")
return 5
def get_password(self, service, username):
"""Get password of the username for the service"""
attributes = {
"application": self.appid,
"service": service,
"username": username,
}
try:
items = Secret.password_search_sync(self.schema, attributes,
Secret.SearchFlags.UNLOCK, None)
except GLib.Error as error:
quark = GLib.quark_try_string('g-io-error-quark')
if error.matches(quark, Gio.IOErrorEnum.FAILED):
raise KeyringLocked('Failed to unlock the item!')
raise error
for item in items:
try:
return item.retrieve_secret_sync().get_text()
except GLib.Error as error:
quark = GLib.quark_try_string('secret-error')
if error.matches(quark, Secret.Error.IS_LOCKED):
raise KeyringLocked('Failed to unlock the item!')
raise error
def set_password(self, service, username, password):
"""Set password for the username of the service"""
collection = Secret.COLLECTION_DEFAULT
attributes = {
"application": self.appid,
"service": service,
"username": username,
}
label = "Password for '{}' on '{}'".format(username, service)
try:
stored = Secret.password_store_sync(self.schema, attributes, collection,
label, password, None)
except GLib.Error as error:
quark = GLib.quark_try_string('secret-error')
if error.matches(quark, Secret.Error.IS_LOCKED):
raise KeyringLocked("Failed to unlock the collection!")
quark = GLib.quark_try_string('g-io-error-quark')
if error.matches(quark, Gio.IOErrorEnum.FAILED):
raise KeyringLocked("Failed to unlock the collection!")
raise error
if not stored:
raise PasswordSetError("Failed to store password!")
def delete_password(self, service, username):
"""Delete the stored password (only the first one)"""
attributes = {
"application": self.appid,
"service": service,
"username": username,
}
try:
items = Secret.password_search_sync(self.schema, attributes,
Secret.SearchFlags.UNLOCK, None)
except GLib.Error as error:
quark = GLib.quark_try_string('g-io-error-quark')
if error.matches(quark, Gio.IOErrorEnum.FAILED):
raise KeyringLocked('Failed to unlock the item!')
for item in items:
try:
removed = Secret.password_clear_sync(self.schema,
item.get_attributes(), None)
except GLib.Error as error:
quark = GLib.quark_try_string('secret-error')
if error.matches(quark, Secret.Error.IS_LOCKED):
raise KeyringLocked('Failed to unlock the item!')
raise error
return removed
raise PasswordDeleteError("No such password!")
def get_credential(self, service, username):
"""Get the first username and password for a service.
Return a Credential instance
The username can be omitted, but if there is one, it will use get_password
and return a SimpleCredential containing the username and password
Otherwise, it will return the first username and password combo that it finds.
"""
query = {"service": service}
if username:
query["username"] = username
items = Secret.password_search_sync(self.schema, query,
Secret.SearchFlags.UNLOCK, None)
for item in items:
username = item.get_attributes().get("username")
return SimpleCredential(username, item.retrieve_secret_sync().get_text())