summaryrefslogtreecommitdiffstats
path: root/src/Gui/ConfBackend.py
blob: bb62da4337346aa44ee07a151a1f8891492d7cea (plain)
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
from abrt_utils import _, log, log1, log2

# Doc on Gnome keyring API:
# http://library.gnome.org/devel/gnome-keyring/stable/
# Python bindings are in gnome-python2-desktop package

try:
    import gnomekeyring as gkey
except ImportError, e:
    gkey = None

# Exceptions
class ConfBackendInitError(Exception):
    def __init__(self, msg):
        Exception.__init__(self)
        self.what = msg

    def __str__(self):
        return self.what

class ConfBackendSaveError(Exception):
    def __init__(self, msg):
        Exception.__init__(self)
        self.what = msg

    def __str__(self):
        return self.what

class ConfBackendLoadError(Exception):
    def __init__(self, msg):
        Exception.__init__(self)
        self.what = msg

    def __str__(self):
        return self.what


class ConfBackend(object):
    def __init__(self):
        pass

    def save(self, name, settings):
        """ Default save method has to be implemented in derived class """
        raise NotImplementedError

    def load(self, name):
        """ Default load method has to be implemented in derived class """
        raise NotImplementedError


# We use Gnome keyring in the following way:
# we store passwords for each plugin in a key named "abrt:<plugin_name>".
# The value of the key becomes the value of "Password" setting.
# Other settings (if plugin has them) are stored as attributes of this key.
#
# Example: Key "abrt:Bugzilla" with bugzilla password as value, and with attributes:
#
# Application: abrt
# AbrtPluginInfo: Bugzilla
# NoSSLVerify: yes
# Login: user@host.com
# BugzillaURL: https://host.with.bz.com/
#
# Attributes "Application" and "AbrtPluginInfo" are special, they are used
# for efficient key retrieval via keyring API find_items_sync() function.

g_default_key_ring = None

class ConfBackendGnomeKeyring(ConfBackend):
    def __init__(self):
        global g_default_key_ring

        ConfBackend.__init__(self)
        if g_default_key_ring:
            return
        if not gkey or not gkey.is_available():
            raise ConfBackendInitError(_("Can't connect to Gnome Keyring daemon"))
        try:
            g_default_key_ring = gkey.get_default_keyring_sync()
        except:
            # could happen if keyring daemon is running, but we run gui under
            # user who is not the owner of the running session - using su
            raise ConfBackendInitError(_("Can't get default keyring"))

    def save(self, name, settings):
        settings_tmp = settings.copy()
        settings_tmp["Application"] = "abrt"
        settings_tmp["AbrtPluginInfo"] = name

        # delete all keyring items containg "AbrtPluginInfo":"<plugin_name>",
        # so we always have only 1 item per plugin
        try:
            item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "AbrtPluginInfo": str(name) })
            for item in item_list:
               log2("found old keyring item: ring:'%s' item_id:%s attrs:%s", item.keyring, item.item_id, str(item.attributes))
               log2("deleting it from keyring '%s'", g_default_key_ring)
               gkey.item_delete_sync(g_default_key_ring, item.item_id)
        except gkey.NoMatchError:
            # nothing found
            pass
        except gkey.DeniedError:
            raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings won't be saved."))
        # if plugin has a "Password" setting, we handle it specially: in keyring,
        # it is stored as item.secret, not as one of attributes
        password = ""
        if "Password" in settings_tmp:
            password = settings_tmp["Password"]
            del settings_tmp["Password"]
        # store new settings for this plugin as one keyring item
        try:
            gkey.item_create_sync(g_default_key_ring,
                                        gkey.ITEM_GENERIC_SECRET,
                                        "abrt:%s" % name, # display_name
                                        settings_tmp, # attrs
                                        password, # secret
                                        True)
        except gkey.DeniedError:
            raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings won't be saved."))

    def load(self, name):
        item_list = None
        #FIXME: make this configurable
        # this actually makes GUI to ask twice per every plugin
        # which have it's settings stored in keyring
        attempts = 2
        while attempts:
            try:
                log2("looking for keyring items with 'AbrtPluginInfo:%s' attr", str(name))
                item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, {"AbrtPluginInfo":str(name)})
                for item in item_list:
                    # gnome keyring is weeeeird. why display_name, type, mtime, ctime
                    # aren't available in find_items_sync() results? why we need to
                    # get them via additional call, item_get_info_sync()?
                    # internally, item has GNOME_KEYRING_TYPE_FOUND type,
                    # and info has GNOME_KEYRING_TYPE_ITEM_INFO type.
                    # why not use the same type for both?
                    #
                    # and worst of all, this information took four hours of googling...
                    #
                    #info = gkey.item_get_info_sync(item.keyring, item.item_id)
                    log2("found keyring item: ring:'%s' item_id:%s attrs:%s", # "secret:'%s' display_name:'%s'"
                            item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name()
                    )
            except gkey.NoMatchError:
                # nothing found
                pass
            except gkey.DeniedError:
                attempts -= 1
                log2("gk-authorization has failed %i time(s)", 2-attempts)
                if attempts == 0:
                    # we tried 2 times, so giving up the authorization
                    raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, can't load the settings for %s!" % name))
                continue
            break

        if item_list:
            retval = item_list[0].attributes.copy()
            retval["Password"] = item_list[0].secret
            return retval
        return {}

    # This routine loads setting for all plugins. It doesn't need plugin name.
    # Thus we can avoid talking to abrtd just in order to get plugin names.
    def load_all(self):
        retval = {}
        item_list = {}

        # UGLY compat cludge for users who has saved items without "Application" attr
        # (abrt <= 1.0.3 was saving those)
        item_ids = gkey.list_item_ids_sync(g_default_key_ring)
        log2("all keyring item ids:%s", item_ids)
        for item_id in item_ids:
            info = gkey.item_get_info_sync(g_default_key_ring, item_id)
            attrs = gkey.item_get_attributes_sync(g_default_key_ring, item_id)
            log2("keyring item %s: attrs:%s", item_id, str(attrs))
            if "AbrtPluginInfo" in attrs:
                if not "Application" in attrs:
                    log2("updating old-style keyring item")
                    attrs["Application"] = "abrt"
                    try:
                        gkey.item_set_attributes_sync(g_default_key_ring, item_id, attrs)
                    except:
                        log2("error updating old-style keyring item")
                plugin_name = attrs["AbrtPluginInfo"]
                # If plugin has a "Password" setting, we handle it specially: in keyring,
                # it is stored as item.secret, not as one of attributes
                if info.get_secret():
                    attrs["Password"] = info.get_secret()
                # avoiding sending useless duplicate info over dbus...
                del attrs["AbrtPluginInfo"]
                try:
                    del attrs["Application"]
                except:
                    pass
                retval[plugin_name] = attrs;
        # end of UGLY compat cludge

        try:
            log2("looking for keyring items with 'Application:abrt' attr")
            item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "Application": "abrt" })
        except gkey.NoMatchError:
            # nothing found
            pass
        except gkey.DeniedError:
            raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, can't load settings"))

        for item in item_list:
            # gnome keyring is weeeeird. why display_name, type, mtime, ctime
            # aren't available in find_items_sync() results? why we need to
            # get them via additional call, item_get_info_sync()?
            # internally, item has GNOME_KEYRING_TYPE_FOUND type,
            # and info has GNOME_KEYRING_TYPE_ITEM_INFO type.
            # why not use the same type for both?
            #
            # and worst of all, this information took four hours of googling...
            #
            #info = gkey.item_get_info_sync(item.keyring, item.item_id)
            log2("found keyring item: ring:%s item_id:%s attrs:%s", # "secret:%s display_name:'%s'"
                    item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name()
            )
            attrs = item.attributes.copy()
            if "AbrtPluginInfo" in attrs:
                plugin_name = attrs["AbrtPluginInfo"]
                # If plugin has a "Password" setting, we handle it specially: in keyring,
                # it is stored as item.secret, not as one of attributes
                if item.secret:
                    attrs["Password"] = item.secret
                # avoiding sending useless duplicate info over dbus...
                del attrs["AbrtPluginInfo"]
                try:
                    del attrs["Application"]
                except:
                    pass
                retval[plugin_name] = attrs
        return retval


# Rudimentary backend factory

currentConfBackend = None

def getCurrentConfBackend():
    global currentConfBackend
    if not currentConfBackend:
        currentConfBackend = ConfBackendGnomeKeyring()
    return currentConfBackend