online-desktop r7255 - in trunk: . weblogindriver weblogindriver/weblogindriver
- From: marinaz svn gnome org
- To: svn-commits-list gnome org
- Subject: online-desktop r7255 - in trunk: . weblogindriver weblogindriver/weblogindriver
- Date: Fri, 20 Jun 2008 17:56:23 +0000 (UTC)
Author: marinaz
Date: Fri Jun 20 17:56:22 2008
New Revision: 7255
URL: http://svn.gnome.org/viewvc/online-desktop?rev=7255&view=rev
Log:
Add onlineaccounts DBus service to the web-login-driver.
Added:
trunk/weblogindriver/weblogindriver/
trunk/weblogindriver/weblogindriver/__init__.py
trunk/weblogindriver/weblogindriver/gutil.py
trunk/weblogindriver/weblogindriver/keyring.py
trunk/weblogindriver/weblogindriver/web-login-driver
- copied unchanged from r7253, /trunk/weblogindriver/web-login-driver
Modified:
trunk/Makefile-weblogindriver.am
trunk/online-desktop.schemas.in
trunk/weblogindriver/web-login-driver
Modified: trunk/Makefile-weblogindriver.am
==============================================================================
--- trunk/Makefile-weblogindriver.am (original)
+++ trunk/Makefile-weblogindriver.am Fri Jun 20 17:56:22 2008
@@ -7,6 +7,11 @@
nssdecrypt_la_SOURCES=weblogindriver/pynssdecrypt.c
+weblogindriverdir=$(pyexecdir)/weblogindriver
+weblogindriver_PYTHON = weblogindriver/weblogindriver/__init__.py \
+ weblogindriver/weblogindriver/keyring.py \
+ weblogindriver/weblogindriver/gutil.py
+
bin_SCRIPTS += weblogindriver/web-login-driver
EXTRA_DIST += weblogindriver/web-login-driver
@@ -18,4 +23,4 @@
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\ bindir\@|$(bindir)|" $< > $@
-DISTCLEANFILES += $(service_DATA)
\ No newline at end of file
+DISTCLEANFILES += $(service_DATA)
Modified: trunk/online-desktop.schemas.in
==============================================================================
--- trunk/online-desktop.schemas.in (original)
+++ trunk/online-desktop.schemas.in Fri Jun 20 17:56:22 2008
@@ -11,5 +11,32 @@
<short>Whether to sync preferences to a network server</short>
</locale>
</schema>
+ <schema>
+ <key>/schemas/desktop/gnome/online-accounts/TEMPLATE/enabled</key>
+ <owner>gnome</owner>
+ <type>bool</type>
+ <default>TRUE</default>
+ <locale name="C">
+ <short>Whether the account is enabled by the user to be utilized by bigboard stocks.</short>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/gnome/online-accounts/TEMPLATE/kind</key>
+ <owner>gnome</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>The type of the account.</short>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/gnome/online-accounts/TEMPLATE/username</key>
+ <owner>gnome</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>The username for the account.</short>
+ </locale>
+ </schema>
</schemalist>
</gconfschemafile>
Modified: trunk/weblogindriver/web-login-driver
==============================================================================
--- trunk/weblogindriver/web-login-driver (original)
+++ trunk/weblogindriver/web-login-driver Fri Jun 20 17:56:22 2008
@@ -1,31 +1,44 @@
#!/usr/bin/python
-import os,sys,re,logging,urlparse,base64
+import os,sys,re,logging,urlparse,base64,gconf,copy
-import gobject, dbus, gnomevfs
+import gobject, dbus, gnomevfs, gobject
import dbus,dbus.service,dbus.glib
-# Custom module
+from ddm import DataModel
+
+# Custom modules
import nssdecrypt
+import weblogindriver.keyring as keyring
+from weblogindriver.gutil import *
_logger = logging.getLogger("WebLoginDriver")
+GCONF_BASE_DIR = "/desktop/gnome/online-accounts"
+
+_accounts_schema_dir = "/schemas" + GCONF_BASE_DIR + "/TEMPLATE"
+
def do_idle(f, *args):
f(*args)
return False
def on_pidgin_gmail(signon):
- p = dbus.SessionBus().get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject')
+ p = None
+ try:
+ p = dbus.SessionBus().get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject')
+ except dbus.DBusException, e:
+ _logger.debug("PurpleService not available")
+ return
target_username = signon['username'] + '@gmail.com'
a = None
for acctid in p.PurpleAccountsGetAllActive():
- username = p.PurpleAccountGetUsername(a)
+ username = p.PurpleAccountGetUsername(acctid)
try:
- (real_username, resource) = username.split('/', 1)
+ (real_username, resource) = username.split('/', 1)
except:
- real_username = username
+ real_username = username
if real_username == target_username:
- a = accountid
+ a = acctid
break
if not a:
_logger.debug("creating pidgin account for %s", target_username)
@@ -35,13 +48,33 @@
p.PurpleAccountSetRememberPassword(a, dbus.UInt32(1))
do_idle(p.PurpleAccountSetEnabled, a, "gtk-gaim", dbus.UInt32(1))
+def associate_schema(schema_dir, new_dir):
+ """
+
+ This function is used when dynamically creating a directory in
+ GConf that should be associated with a fixed schema.
+
+ schema_dir -- the directory where the schema is stored
+ new_dir -- the directory to associate the schema with
+
+ """
+
+ client = gconf.client_get_default()
+ engine = gconf.engine_get_default()
+ _logger.debug("associating schema schema_dir %s" % schema_dir)
+ for entry in client.all_entries(schema_dir):
+ _logger.debug("entry key %s" % entry.get_key())
+ # There is a schema for each individual key,
+ key = os.path.join(new_dir, os.path.basename(entry.get_key()))
+ engine.associate_schema(key, entry.get_key())
+
hints = {
'GMail': {'hostname': 'https://www.google.com', 'username_field': 'Email'},
'GAFYD-Mail': {'hostname': 'https://www.google.com', 'username_field': 'userName'},
}
internal_matchers = {
- 'GMail': on_pidgin_gmail,
+ #'GMail': on_pidgin_gmail, # this causes invalid gmail accounts that are stuck in Firefox to be added to pidgin
}
BUS_NAME_STR='org.gnome.WebLoginDriver'
@@ -269,15 +302,714 @@
def GetSignon(self, signon):
return self.__signons[signon]
+KIND_GOOGLE = "google"
+KIND_TWITTER = "twitter"
+KIND_RTM = "rtm"
+KIND_LIBRARY_THING = "library"
+
+ONLINE_ACCOUNT_KINDS = {KIND_GOOGLE : "Google", KIND_TWITTER : "Twitter", KIND_RTM : "Remember The Milk", KIND_LIBRARY_THING : "LibraryThing"}
+
+class OnlineAccount(dbus.service.Object):
+ def __init__(self, account_id, bus_name, kind, username, password='', enabled=True, gconf_dir=None):
+ dbus.service.Object.__init__(self, object_path = '/onlineaccount/' + str(account_id), bus_name = bus_name)
+ # TODO: check if dbus.service.Object really doesn't have bus_name as a field
+ self.__account_id = account_id
+ self.__bus_name = bus_name
+ self.__kind = kind
+ self.__username = username
+ self.__password = password
+ self.__enabled = enabled
+ self.__gconf_dir = gconf_dir
+
+ _logger.debug("Created account %s" % (str(self)))
+
+ # copy and deepcopy might not make much sense for this class, since each instance of the class should have
+ # a unique account_id
+ def __copy__(self):
+ return OnlineAccount(self.__account_id, self.__bus_name, self.__kind, self.__username, self.__password, self.__enabled, self.__gconf_dir)
+
+ def __deepcopy__(self, memo):
+ return OnlineAccount(copy.deepcopy(self.__account_id, memo), copy.deepcopy(self.__bus_name, memo), \
+ copy.deepcopy(self.__kind, memo), copy.deepcopy(self.__username, memo), \
+ copy.deepcopy(self.__password, memo), copy.deepcopy(self.__enabled, memo), \
+ copy.deepcopy(self.__gconf_dir, memo))
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="s")
+ def GetKind(self):
+ return self.__kind
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="s")
+ def GetUsername(self):
+ return self.__username
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="s")
+ def GetPassword(self):
+ return self.__password
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="b")
+ def GetEnabled(self):
+ return self.__enabled
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="o")
+ def GetObjectPath(self):
+ return self._object_path
+
+ @dbus.service.signal(BUS_IFACE_STR,
+ signature='')
+ def Changed(self):
+ pass
+
+ def _get_gconf_dir(self):
+ return self.__gconf_dir
+
+ def _update_from_origin(self, new_props):
+ _logger.debug("updating account from origin")
+ changed = False
+ for (key,value) in new_props.items():
+ if key not in ["password", "enabled", "gconf_dir"]:
+ _logger.error("key %s is unknown or can't be changed" % key)
+ continue
+
+ if value is None:
+ value = ''
+
+ if key != "password":
+ _logger.debug("key %s, value %s" % (key, value))
+ else:
+ _logger.debug("key %s, value length %d" % (key, len(value)))
+
+ old = getattr(self, '_OnlineAccount__' + key)
+ if old != value:
+ setattr(self, '_OnlineAccount__' + key, value)
+ changed = True
+
+ # TODO: figure out if it makes sense to emit a changed signal if gconf_dir changed
+ if changed:
+ _logger.debug("emitting change!")
+ self.Changed()
+
+ def __str__(self):
+ return "<Account userame:%s, kind:%s, gconf_dir:%s, enabled:%s>" % (self.GetUsername(), self.GetKind(), self._get_gconf_dir(), self.GetEnabled())
+
+
+class OnlineAccounts(dbus.service.Object):
+ def __init__(self, bus_name):
+ dbus.service.Object.__init__(self, bus_name, '/onlineaccounts')
+ self.__bus_name = bus_name
+ self.__received_response_from_server = set()
+
+ #TODO: do we need to pass in the server name, and if so how do we know here what to pass in?
+ self.__model = DataModel()
+ self.__model.add_ready_handler(self.__on_ready)
+
+ self.__server_accounts = set()
+ self.__gconf_accounts = set()
+ self.__weblogin_accounts = set()
+ self.__enabled_accounts = set()
+
+ # This is a mapping from object paths to OnlineAccount objects for all accounts.
+ # It would have been nice to use the same id as the gconf key id, but we create
+ # accounts before we add them to gconf, and also we create accounts for the
+ # signons returned by web login driver, which we don't add to gconf.
+ self.__all_accounts = {}
+ self.__account_id = None
+
+ try:
+ self.__weblogindriver_proxy = dbus.SessionBus().get_object('org.gnome.WebLoginDriver', '/weblogindriver')
+ self.__weblogindriver_proxy.connect_to_signal("SignonChanged",
+ self.__on_signon_changed)
+ except dbus.DBusException, e:
+ _logger.debug("weblogindriver not available")
+ self.__weblogindriver_proxy = None
+
+ if self.__weblogindriver_proxy:
+ self.__recheck_signons()
+
+ self.__gconf = gconf.client_get_default()
+ self.__gconf.add_dir(GCONF_BASE_DIR, gconf.CLIENT_PRELOAD_RECURSIVE)
+ self.__gconf.notify_add(GCONF_BASE_DIR, self.__on_gconf_change)
+
+ ## a dict from gconf directory name underneath GCONF_BASE_DIR to
+ ## a dict of the gconf values underneath that account directory
+ self.__gconf_info = {}
+
+ self.__reload_from_gconf()
+
+ if self.__model.ready:
+ self.__on_ready()
+
+ @dbus.service.signal(BUS_IFACE_STR,
+ signature='o')
+ def AccountAdded(self, account_object_path):
+ pass
+
+ @dbus.service.signal(BUS_IFACE_STR,
+ signature='o')
+ def AccountRemoved(self, account_object_path):
+ pass
+
+ @dbus.service.signal(BUS_IFACE_STR,
+ signature='o')
+ def AccountEnabled(self, account_object_path):
+ pass
+
+ @dbus.service.signal(BUS_IFACE_STR,
+ signature='o')
+ def AccountDisabled(self, account_object_path):
+ pass
+
+ def __create_online_account(self, *args, **kwargs):
+ if self.__account_id is None:
+ self.__account_id = 0
+ else:
+ self.__account_id = self.__account_id + 1
+ account = OnlineAccount(self.__account_id, self.__bus_name, *args, **kwargs)
+ self.__all_accounts[account.GetObjectPath()] = account
+ return account
+
+ def __find_weblogin_account(self, kind, username):
+ for a in self.__weblogin_accounts:
+ _logger.debug("comparing to a weblogin account %s bool %s a.GetKind() %s kind %s" % (a, a.GetKind() == kind, a.GetKind(), kind))
+ if a.GetKind() == kind and a.GetUsername() == username:
+ return a
+
+ return None
+
+ def __find_account_by_gconf_dir(self, gconf_dir):
+ for a in self.__gconf_accounts:
+ if a._get_gconf_dir() == gconf_dir:
+ return a
+
+ return None
+
+ def __get_gconf_accounts_by_kind(self, kind):
+ gconf_accounts_by_kind = set()
+ for a in self.__gconf_accounts:
+ if a.GetKind() == kind:
+ gconf_accounts_by_kind.add(a)
+ return gconf_accounts_by_kind
+
+ def __get_server_accounts_by_kind(self, kind):
+ server_accounts_by_kind = set()
+ for a in self.__server_accounts:
+ if a.GetKind() == kind:
+ server_accounts_by_kind.add(a)
+ return server_accounts_by_kind
+
+ def __update_account(self, account):
+ _logger.debug("Updating account %s" % (str(account)))
+
+ ## note that "kind" and "username" are never updated (not allowed to change without
+ ## making a new Account object)
+
+ was_enabled = account in self.__enabled_accounts
+
+ fields = {}
+
+ gconf_dir = account._get_gconf_dir()
+
+ if account in self.__server_accounts:
+ self.__ensure_account_in_gconf(account)
+ elif account.GetKind() in self.__received_response_from_server:
+ #remove gconf dir if one exists
+ if gconf_dir:
+ self.__remove_gconf_dir(gconf_dir)
+
+ ## first, look for the info from our local gconf storage
+ if gconf_dir and gconf_dir in self.__gconf_info:
+ gconf_info = self.__gconf_info[gconf_dir]
+
+ if 'enabled' in gconf_info:
+ fields['enabled'] = gconf_info['enabled']
+
+ elif gconf_dir and gconf_dir not in self.__gconf_info:
+ ## Account object originating with a gconf item
+ ## apparently now deleted, so disable. This
+ ## does mean if you create a gconf item corresponding
+ ## to something from weblogin driver, then delete the
+ ## gconf item, the weblogin account doesn't come back
+ ## until you restart the process. Clunky.
+ ## deleting a gconf dir isn't what we'd normally do though,
+ ## we'd normally set enabled=False in gconf, which would
+ ## persist across restarts.
+ self.__gconf_accounts.remove(account)
+ if was_enabled:
+ self.__enabled_accounts.remove(account)
+ self.AccountDisabled(account.GetObjectPath())
+ del self.__all_accounts[account.GetObjectPath()]
+ self.AccountRemoved(account.GetObjectPath())
+ return
+
+ ## after compositing all this information, update our account object
+ account._update_from_origin(fields)
+
+ ## second, look at weblogin driver, though we don't want to prefer
+ ## its password over keyring, so there's some trickiness
+ weblogin_password = None
+ _logger.debug("will look for a weblogin account with kind %s username %s" % (account.GetKind(), account.GetUsername()))
+ weblogin_account = self.__find_weblogin_account(account.GetKind(), account.GetUsername())
+ if weblogin_account:
+ _logger.debug("weblogin account found")
+ weblogin_password = weblogin_account.GetPassword()
+
+ ## use updated information to find password
+ fields = {}
+
+ ## third, look for password in keyring
+ _logger.debug("here 1")
+ k = keyring.get_keyring()
+ _logger.debug("here 2")
+ password = k.get_password(kind=account.GetKind(),
+ username=account.GetUsername(),
+ url="")
+ _logger.debug("here 3")
+ if password:
+ _logger.debug("using password from keyring")
+ fields['password'] = password
+
+ ## fourth, if no password in keyring, use the weblogin one
+ if weblogin_password and 'password' not in fields:
+ _logger.debug("using password from weblogin")
+ fields['password'] = weblogin_password
+
+ ## if no password found, the password has to be set to empty
+ if 'password' not in fields:
+ fields['password'] = ''
+
+ ## update account object again with the password
+ account._update_from_origin(fields)
+
+ ## now add or remove the account from the set of enabled accounts
+ if was_enabled and not account.GetEnabled():
+ self.__enabled_accounts.remove(account)
+ self.AccountDisabled(account.GetObjectPath())
+ elif not was_enabled and account.GetEnabled():
+ self.__enabled_accounts.add(account)
+ _logger.debug("will emit account enabled signal")
+ self.AccountEnabled(account.GetObjectPath())
+
+ def __remove_gconf_dir(self, gconf_dir):
+ base_key = GCONF_BASE_DIR + '/' + gconf_dir
+ success = self.__gconf.recursive_unset(base_key, gconf.UNSET_INCLUDING_SCHEMA_NAMES)
+ _logger.debug("removed gconf dir %s success %s" % (base_key, success))
+ self.__gconf.suggest_sync()
+ if self.__gconf_info.has_key(gconf_dir):
+ del self.__gconf_info[gconf_dir]
+
+ def __try_ensure_and_update_account_for_gconf_dir(self, gconf_dir):
+ account = self.__find_account_by_gconf_dir(gconf_dir)
+ _logger.debug("ensuring account for %s gconf key %s", account, gconf_dir);
+ if account:
+ self.__update_account(account)
+ return
+
+ if gconf_dir not in self.__gconf_info:
+ _logger.error("trying to create Account for a gconf dir that doesn't exist")
+ return
+
+ gconf_info = self.__gconf_info[gconf_dir]
+ if 'kind' not in gconf_info or 'username' not in gconf_info:
+ _logger.error("gconf account has no kind or username setting")
+ self.__remove_gconf_dir(gconf_dir)
+ return
+
+ kind = gconf_info['kind']
+ username = gconf_info['username']
+ if kind not in ONLINE_ACCOUNT_KINDS.keys():
+ _logger.error("unknown account kind in gconf")
+ self.__remove_gconf_dir(gconf_dir)
+ return
+
+ # account = self.__find_weblogin_account_by_kind(kind)
+ # if account:
+ # account._update_from_origin({"gconf_dir" : gconf_dir})
+ # else:
+ account = self.__create_online_account(kind, username, gconf_dir=gconf_dir, enabled=False)
+
+ self.__gconf_accounts.add(account)
+ self.AccountAdded(account.GetObjectPath())
+
+ self.__update_account(account)
+
+ def __remove_dirname(self, gconf_key):
+ i = gconf_key.rfind('/')
+ return gconf_key[i+1:]
+
+ def __on_ready(self):
+ if self.__model.self_resource != None:
+ _logger.debug("will get online desktop accounts")
+ # TODO: get all gnomeExternalAccounts here later, googleEnabledEmails is just one case
+ query = self.__model.query_resource(self.__model.self_resource, "googleEnabledEmails +")
+ query.add_handler(self.__on_google_enabled_emails)
+ query.add_error_handler(self.__on_datamodel_error)
+ query.execute()
+
+ def __on_datamodel_error(self, code, str):
+ _logger.error("datamodel error %s: %s", code, str)
+
+ def __on_google_enabled_emails(self, myself):
+ _logger.debug("received some google enabled emails")
+ myself.connect(self.__update_google_enabled_emails, 'googleEnabledEmails')
+ self.__update_google_enabled_emails(myself)
+
+ def __update_google_enabled_emails(self, myself):
+ new_google_accounts = set()
+ if not hasattr(myself, 'googleEnabledEmails'):
+ _logger.debug("No googleEnabledEmails in DDM identity")
+ elif len(myself.googleEnabledEmails) == 0:
+ _logger.debug("DDM identity has 0 googleEnabledEmails")
+ else:
+ for email in myself.googleEnabledEmails:
+ username = str(email)
+ _logger.debug("got a googleEnabledEmail %s", email)
+ new_google_accounts.add(self.__create_online_account(KIND_GOOGLE, username))
+
+ self.update_accounts_from_server(KIND_GOOGLE, new_google_accounts)
+
+ def update_accounts_from_server(self, account_kind, new_accounts):
+ existing_accounts = self.__get_server_accounts_by_kind(account_kind)
+
+ for new_account in new_accounts:
+ # if acccount already in the list of server_accounts, don't do anything
+ # if it is new, add it, and update information for it
+ # if it is no longer returned by the server, remove it and remove it from other
+ # lists, including gconf
+ account_found = False
+ for existing_account in existing_accounts:
+ if existing_account.GetUsername() == new_account.GetUsername():
+ # we found it, we don't need to change anything about it
+ account_found = True
+ existing_accounts.remove(existing_account)
+ break
+
+ if not account_found:
+ new_account_to_add = self.__find_account_in_gconf(new_account.GetKind(), new_account.GetUsername())
+ if new_account_to_add is None:
+ new_account_to_add = new_account # we used to use copy.deepcopy here, not sure we need it
+ self.__server_accounts.add(new_account_to_add)
+ # this will add the account to gconf and enabled accounts, and check if
+ # we have a password for it
+ self.__update_account(new_account_to_add)
+
+ # clear out accounts that are no longer found in the list of accounts of this kind from the server
+ for existing_account in existing_accounts:
+ self.__server_accounts.remove(existing_account)
+ # this should remove the account from gconf and gconf_accounts
+ self.__update_account(existing_account)
+
+ self.__received_response_from_server.add(account_kind)
+
+ # make sure that all accounts in gconf correspond to the ones returned from the server;
+ # this will remove old accounts from gconf
+ existing_accounts_in_gconf = self.__get_gconf_accounts_by_kind(account_kind)
+ _logger.debug("existing_accounts_in_gconf of type %s: %d" % (account_kind, len(existing_accounts_in_gconf)))
+ for gconf_account in existing_accounts_in_gconf:
+ self.__update_account(gconf_account)
+
+ def __get_gconf_info(self, gconf_dir):
+ base_key = GCONF_BASE_DIR + "/" + gconf_dir
+ gconf_info = {}
+ def get_account_prop(gconf, gconf_info, base_key, prop):
+ try:
+ value = gconf.get_value(base_key + '/' + prop)
+ except ValueError:
+ value = None
+ if value:
+ # _logger.debug("key %s, value %s" % (prop, value))
+ gconf_info[prop] = value
+ get_account_prop(self.__gconf, gconf_info, base_key, 'kind')
+ get_account_prop(self.__gconf, gconf_info, base_key, 'username')
+ get_account_prop(self.__gconf, gconf_info, base_key, 'enabled')
+ # GConf returns None for get_value if the vale is False
+ if 'enabled' not in gconf_info:
+ gconf_info['enabled'] = False
+ return gconf_info
+
+ def __reload_from_gconf(self):
+ gconf_dirs = self.__gconf.all_dirs(GCONF_BASE_DIR)
+
+ _logger.debug("Reloading %s from gconf" % (str(gconf_dirs)))
+
+ new_gconf_infos = {}
+ for gconf_dir in gconf_dirs:
+ base_key = gconf_dir
+ gconf_dir = self.__remove_dirname(gconf_dir)
+ new_gconf_infos[gconf_dir] = self.__get_gconf_info(gconf_dir)
+
+ # GConf might already have some settings that did not have a schema.
+ # This portion is only for bootstrapping these settings to have a schema.
+ # We check if one of the entries that must be there (username) has a schema,
+ # and associate a schema with the whole directory if it doesn't.
+ username_entry = self.__gconf.get_entry(base_key + '/username', '', True)
+ if username_entry and not username_entry.get_schema_name():
+ associate_schema(_accounts_schema_dir, base_key)
+ elif not username_entry:
+ _logger.warn("We expected username entry to be in %s, but it was not found" % base_key)
+
+ self.__gconf_info = new_gconf_infos
+
+ ## create any new accounts
+ gconf_info_keys = copy.copy(self.__gconf_info.keys())
+ for gconf_dir in gconf_info_keys:
+ self.__try_ensure_and_update_account_for_gconf_dir(gconf_dir)
+
+ ## now update any old accounts that are no longer in gconf,
+ ## which should result in enabled=False
+ for a in self.__gconf_accounts:
+ gconf_dir = a._get_gconf_dir()
+ _logger.debug("processing gconf account for %s" % gconf_dir)
+ if gconf_dir and gconf_dir not in self.__gconf_info:
+ self.__update_account(a)
+
+ @defer_idle_func(timeout=400)
+ def __on_gconf_change(self, *args):
+ _logger.debug("gconf change notify for accounts")
+ self.__reload_from_gconf()
+
+ def __check_signons(self, signons):
+ # this processes signons for a specific hostname
+ # TODO: extend this to store signons for different types of accounts once we are ready to support them;
+ # make sure we store signon information for GAFYD accounts
+ # actually, it looks like GAFYD accounts are indistinguishable in Firefox from regular Google accounts
+ for signon in signons:
+ if 'hint' not in signon: continue
+ if signon['hint'] == 'GMail' or signon['hint'] == 'GAFYD-Mail':
+ username = str(signon['username'])
+ if signon['hint'] == 'GMail':
+ username = str(signon['username']) + "@gmail.com"
+ password = base64.b64decode(signon['password'])
+ _logger.debug("got signon information %s" % str(signon))
+ account = self.__find_weblogin_account(KIND_GOOGLE, username)
+ _logger.debug("found weblogin account %s" % str(account))
+ if not account:
+ account = self.__create_online_account(KIND_GOOGLE, username, enabled=False, password=password)
+ self.__weblogin_accounts.add(account)
+ self.__update_account(account)
+ elif password != account.GetPassword():
+ account._set_password(password)
+ self.__update_account(account)
+
+ def __recheck_signons(self):
+ self.__weblogindriver_proxy.GetSignons(reply_handler=self.__on_get_signons_reply,
+ error_handler=self.__on_dbus_error)
+
+ def __on_get_signons_reply(self, signondata):
+ _logger.debug("got signons reply")
+ for hostname,signons in signondata.iteritems():
+ self.__check_signons(signons)
+
+ def __on_signon_changed(self, signons):
+ # TODO: __on_signon_changed is called with all signons for a given hostname.
+ # However, we need the hostname to be passed in as an argument, since it is needed
+ # for the case when no more signons remain for a given hostname.
+ # We should make this change, but for now, we just never remove existing weblogin accounts.
+ # (The list will be re-created when the bigboard is restarted.) We will update a password
+ # for an existing weblogin account if the updated signon information is passed in here.
+ # Once we make this change, we'll need to make sure to associate the right hostnames with
+ # weblogin accounts, in case they don't match what we store in the url.
+ _logger.debug("signons changed: %s", signons)
+ self.__check_signons(signons)
+
+ def __on_dbus_error(self, err):
+ self.__logger.error("D-BUS error: %s", err)
+
+ def __find_unused_gconf_dir(self, kind):
+ ## find an unused gconf dir
+ i = 0
+ while True:
+ gconf_dir = kind + "_" + str(i)
+ if not self.__find_account_by_gconf_dir(gconf_dir):
+ _logger.debug("returning %s for unused gconf_dir" % gconf_dir)
+ return gconf_dir
+ else:
+ i = i + 1
+
+ def __find_account_in_gconf(self, kind, username):
+ for a in self.__gconf_accounts:
+ if a.GetKind() == kind and a.GetUsername() == username:
+ return a
+ return None
+
+ # new_properties is optional, it should be passed in if there are changes
+ # to an existing account and we will update other data constructs that contain
+ # this account based on the information from gconf
+ #
+ # an account object passed in to this method must have the gconf_dir set or must not be found in gconf
+ def __ensure_account_in_gconf(self, account, new_properties={}):
+ gconf_dir = account._get_gconf_dir()
+ new_gconf_dir = False
+
+ # create a new GConf dir if it is a completely new account
+ if not gconf_dir:
+ if self.__find_account_in_gconf(account.GetKind(), account.GetUsername()):
+ _logger.error("found an account in gconf that matches the passed in account object %s, but the passed in account object is missing a gconf_dir" % str(account))
+ return
+
+ gconf_dir = self.__find_unused_gconf_dir(account.GetKind())
+ new_gconf_dir = True
+ account._update_from_origin({"gconf_dir" : gconf_dir})
+ self.__gconf_accounts.add(account)
+
+ base_key = GCONF_BASE_DIR + '/' + gconf_dir
+
+ _logger.debug("base_key is %s" % base_key)
+
+ def set_account_prop(base_key, prop, value):
+ _logger.debug("prop %s value %s" % (prop, str(value)))
+ if isinstance(value, bool):
+ self.__gconf.set_bool(base_key + '/' + prop, value)
+ elif isinstance(value, str):
+ self.__gconf.set_string(base_key + '/' + prop, value)
+ else:
+ _logger.error("prop %s with value %s has an unexpected type %s" % (prop, str(value), type(value)))
+
+ if new_gconf_dir:
+ set_account_prop(base_key, 'kind', account.GetKind())
+ set_account_prop(base_key, 'username', account.GetUsername())
+
+ ## enable it last, so we ignore the other settings until we do this
+ if 'enabled' in new_properties:
+ _logger.debug("setting new enabled property to be %s" % new_properties['enabled'])
+ set_account_prop(base_key, 'enabled', new_properties['enabled'])
+ elif new_gconf_dir:
+ set_account_prop(base_key, 'enabled', account.GetEnabled())
+
+ if new_gconf_dir:
+ associate_schema(_accounts_schema_dir, base_key)
+ self.__gconf_info[gconf_dir]=self.__get_gconf_info(gconf_dir)
+ self.AccountAdded(account.GetObjectPath())
+
+ # new_properties is a dictionary, valid properties are "password" and "enabled"
+ @dbus.service.method(BUS_IFACE_STR,
+ in_signature="oa{ss}")
+ def SaveAccountChanges(self, account_object_path, new_properties):
+
+ properties = {}
+ for (key, value) in new_properties.items():
+ if key == 'enabled':
+ properties[str(key)] = (value == 'True')
+ else:
+ properties[str(key)] = str(value)
+
+ account = None
+ if self.__all_accounts.has_key(account_object_path):
+ account = self.__all_accounts[account_object_path]
+ else:
+ _logger.error("SaveAccountChanges was called with an unknown object path %s" % account_object_path)
+ return
+
+ _logger.debug("Saving new props for account %s: %s" % (str(account), str(properties.keys())))
+
+ ## special-case handling of password since it goes in the keyring
+ if 'password' in properties:
+ username = account.GetUsername()
+ url=''
+ _logger.debug("here 4")
+ k = keyring.get_keyring()
+ _logger.debug("here 5")
+ k.store_login(kind=account.GetKind(),
+ username=username,
+ url=url,
+ password=properties['password'])
+ _logger.debug("here 6")
+
+ ## now do everything else by stuffing it in gconf
+ self.__ensure_account_in_gconf(account, properties)
+
+ ## keyring doesn't have change notification so we have to do the work for it
+ ## if the password was the only thing that got updated, otherwise we should wait
+ ## till gconf is reloaded
+ if 'password' in properties and (len(properties) == 1 or ('enabled' in properties and properties['enabled'] == account.GetEnabled())):
+ ## this should notice a new password
+ self.__update_account(account)
+
+ # return a tuple with an account object path as the first element, and a boolean indicating if a new account was created
+ @dbus.service.method(BUS_IFACE_STR,
+ in_signature="ss",
+ out_signature="(ob)")
+ def GetOrCreateAccount(self, kind, username):
+ kind = str(kind)
+ username = str(username)
+ account = self.__find_account_in_gconf(kind, username)
+ if account:
+ return (account.GetObjectPath(), False)
+
+ account = self.__create_online_account(kind, username)
+ self.__ensure_account_in_gconf(account)
+ self.__update_account(account) # this might find a password for the account and emit AccountEnabled signal
+ return (account.GetObjectPath(), True)
+
+ @dbus.service.method(BUS_IFACE_STR,
+ in_signature="o")
+ def RemoveAccount(self, account):
+ account = None
+ if self.__all_accounts.has_key(account_object_path):
+ account = self.__all_accounts[account_object_path]
+ else:
+ _logger.error("SaveAccountChanges was called with an unknown object path %s" % account_object_path)
+ return
+
+ _logger.debug("will remove account with gconf_dir %s" % account._get_gconf_dir())
+ self.__remove_gconf_dir(account._get_gconf_dir())
+ # TODO: also remove the account on the server
+ if account in self.__server_accounts:
+ self.__server_accounts.remove(account)
+ # self.__update_account will remove the account from self.__all_accounts
+ self.__update_account(account)
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="ao")
+ def GetEnabledAccounts(self):
+ object_paths_list = [a.GetObjectPath() for a in self.__enabled_accounts]
+ return object_paths_list
+
+ @dbus.service.method(BUS_IFACE_STR,
+ in_signature="as",
+ out_signature="ao")
+ def GetEnabledAccountsWithKinds(self, kinds):
+ object_paths_list = [a.GetObjectPath() for a in \
+ filter(lambda account: account.GetKind() in kinds, self.__enabled_accounts)]
+ return object_paths_list
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="ao")
+ def GetAllAccounts(self):
+ # we use self.__gconf_accounts here, because self.__all_accounts also has random accounts
+ # from the WebLoginDriver
+ object_paths_list = [a.GetObjectPath() for a in self.__gconf_accounts]
+ return object_paths_list
+
+ @dbus.service.method(BUS_IFACE_STR,
+ in_signature="as",
+ out_signature="ao")
+ def GetAllAccountsWithKinds(self, kinds):
+ object_paths_list = [a.GetObjectPath() for a in \
+ filter(lambda account: account.GetKind() in kinds, self.__gconf_accounts)]
+ return object_paths_list
+
+ @dbus.service.method(BUS_IFACE_STR,
+ out_signature="a{ss}")
+ def GetAllAccountKinds(self):
+ return ONLINE_ACCOUNT_KINDS
+
_driver = None
+_online_accounts = None
def modmain():
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(BUS_NAME_STR, bus=bus)
global _driver
_driver = WebLoginDriver(bus_name)
+ global _online_accounts # so that it doesn't get garbage collected
+ _online_accounts = OnlineAccounts(bus_name)
-def main():
- logging.basicConfig()
+def main():
+ gobject.set_application_name("WebLoginDriver")
+ logging.basicConfig(level=logging.DEBUG)
gobject.threads_init()
dbus.glib.threads_init()
modmain()
Added: trunk/weblogindriver/weblogindriver/__init__.py
==============================================================================
Added: trunk/weblogindriver/weblogindriver/gutil.py
==============================================================================
--- (empty file)
+++ trunk/weblogindriver/weblogindriver/gutil.py Fri Jun 20 17:56:22 2008
@@ -0,0 +1,89 @@
+import os, sys, logging, weakref, functools
+from StringIO import StringIO
+
+import gobject
+
+def _run_logging(f, logger, *args):
+ try:
+ f(*args)
+ except:
+ logger.exception('Exception in idle')
+ return False
+
+class DisconnectSet(object):
+ def __init__(self):
+ super(DisconnectSet, self).__init__()
+ self.__connections = set()
+
+ def add(self, object, id):
+ self.__connections.add((object, id))
+
+ def disconnect_object(self, object_to_disconnect):
+ to_remove = []
+ for (object, id) in self.__connections:
+ if object == object_to_disconnect:
+ object.disconnect(id)
+ to_remove.append((object, id))
+
+ for (object, id) in to_remove:
+ self.__connections.remove((object, id))
+
+ def disconnect_all(self):
+ for (object, id) in self.__connections:
+ object.disconnect(id)
+
+def call_timeout(timeout, func, *args, **kwargs):
+ if 'logger' in kwargs:
+ logger = kwargs['logger']
+ del kwargs['logger']
+ else:
+ logger = logging
+ return gobject.timeout_add(timeout, functools.partial(_run_logging, func, logger, *args), **kwargs)
+
+def call_idle(func, *args, **kwargs):
+ return call_timeout(0, func, *args, **kwargs)
+
+_global_call_once_funcs = {}
+def _run_removing_from_call_once(f):
+ try:
+ f()
+ finally:
+ del _global_call_once_funcs[f]
+
+def call_timeout_once(timeout, func, **kwargs):
+ """Call given func exactly once in the next idle time; if func is already pending,
+ it will not be queued multiple times."""
+
+ if func in _global_call_once_funcs:
+ return
+ id = call_timeout(timeout, _run_removing_from_call_once, func, **kwargs)
+ _global_call_once_funcs[func] = id
+ return id
+
+def call_idle_once(func, **kwargs):
+ return call_timeout_once(0, func, **kwargs)
+
+def defer_idle_func(timeout=100, **kwargs):
+ def wrapped(f):
+ return lambda *margs: call_timeout_once(timeout, functools.partial(f, *margs), **kwargs)
+ return wrapped
+
+def read_subprocess_idle(args, cb):
+ import subprocess
+ subp = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
+ buf = StringIO()
+ watchid = None
+ def handle_data_avail(src, condition):
+ if (condition & gobject.IO_IN):
+ buf.write(os.read(src, 8192))
+ if ((condition & gobject.IO_HUP) or (condition & gobject.IO_ERR)):
+ cb(buf.getvalue())
+ os.close(src)
+ subp.wait()
+ gobject.source_remove(watchid)
+ return False
+ return True
+ watchid = gobject.io_add_watch(subp.stdout.fileno(), gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP, handle_data_avail)
+ return watchid
+
+__all__ = ['call_timeout', 'call_idle', 'call_timeout_once', 'call_idle_once', 'defer_idle_func', 'read_subprocess_idle', 'DisconnectSet']
Added: trunk/weblogindriver/weblogindriver/keyring.py
==============================================================================
--- (empty file)
+++ trunk/weblogindriver/weblogindriver/keyring.py Fri Jun 20 17:56:22 2008
@@ -0,0 +1,175 @@
+import logging, gnomekeyring
+
+_logger = logging.getLogger("weblogindriver.Keyring")
+
+class KeyringItem(object):
+ def __init__(self, kind, username='', url='', password=''):
+ super(KeyringItem, self).__init__()
+ self.__kind = kind
+ self.__username = username
+ self.__url = url
+ self.__password = password
+
+ def get_kind(self):
+ return self.__kind
+
+ def get_username(self):
+ return self.__username
+
+ def get_url(self):
+ return self.__url
+
+ def get_password(self):
+ return self.__password
+
+ def set_kind(self, kind):
+ self.__kind = kind
+
+ def set_username(self, username):
+ self.__username = username
+
+ def set_url(self, url):
+ self.__url = url
+
+ def set_password(self, password):
+ self.__password = password
+
+ def __repr__(self):
+ return '{kind=%s username=%s url=%s len(password)=%d}' % (self.__kind, self.__username, self.__url, len(self.__password))
+
+### The keyring is a map from the tuple (kind,username,url) to a password.
+### In gnome-keyring itself we add to the tuple appname=WebLoginDriver
+class Keyring:
+ def __init__(self, is_singleton):
+ if not is_singleton == 42:
+ raise Exception("use keyring.get_keyring()")
+
+ ### an in-memory substitute for gnome-keyring, set of KeyringItem, used
+ ### when the real keyring is not available
+ self.__fallback_items = set()
+
+ def is_available(self):
+ return gnomekeyring.is_available()
+
+ # Returns a set of KeyringItem
+ def get_logins(self, kind, username, url):
+ matches = set()
+
+ if not self.is_available():
+ for ki in self.__fallback_items:
+ if ki.get_kind() == kind and \
+ ki.get_username() == username and \
+ ki.get_url() == url:
+ matches.add(ki)
+
+ else:
+
+ try:
+ found = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET,
+ dict(appname='WebLoginDriver',
+ kind=kind,
+ username=username,
+ url=url))
+ except gnomekeyring.NoMatchError:
+ found = set()
+
+ for f in found:
+ ki = KeyringItem(kind=f.attributes['kind'],
+ username=f.attributes['username'],
+ url=f.attributes['url'],
+ password=f.secret)
+ matches.add(ki)
+
+ return matches
+
+ def get_password(self, kind, username, url):
+ logins = self.get_logins(kind, username, url)
+ _logger.debug("got logins: %s" % (str(logins)))
+ if len(logins) > 0:
+ return logins.pop().get_password()
+ else:
+ return None
+
+ def remove_logins(self, kind, username, url):
+ _logger.debug("removing login (%s, %s, %s)" % (kind, username, url))
+ new_fallbacks = set()
+ for ki in self.__fallback_items:
+ if ki.get_kind() == kind and \
+ ki.get_username() == username and \
+ ki.get_url() == url:
+ pass
+ else:
+ new_fallbacks.add(ki)
+
+ self.__fallback_items = new_fallbacks
+
+ if self.is_available():
+ try:
+ found = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET,
+ dict(appname='WebLoginDriver',
+ kind=kind,
+ username=username,
+ url=url))
+ except gnomekeyring.NoMatchError:
+ found = set()
+
+ for f in found:
+ gnomekeyring.item_delete_sync('session', f.item_id)
+
+ def store_login(self, kind, username, url, password):
+
+ if not password:
+ self.remove_logins(kind, username, url)
+ return
+
+ _logger.debug("storing login (%s, %s, %s)" % (kind, username, url))
+ if not self.is_available():
+ found = None
+ for ki in self.__fallback_items:
+ if ki.get_kind() == kind and \
+ ki.get_username() == username and \
+ ki.get_url() == url:
+ found = ki
+
+ if found:
+ found.set_password(password)
+ else:
+ ki = KeyringItem(kind=kind,
+ username=username,
+ url=url,
+ password=password)
+ self.__fallback_items.add(ki)
+
+ else:
+ keyring_item_id = gnomekeyring.item_create_sync('session',
+ gnomekeyring.ITEM_GENERIC_SECRET,
+ "WebLoginDriver",
+ dict(appname="WebLoginDriver",
+ kind=kind,
+ username=username,
+ url=url),
+ password, True)
+
+keyring_inst = None
+def get_keyring():
+ global keyring_inst
+ if keyring_inst is None:
+ keyring_inst = Keyring(42)
+ return keyring_inst
+
+
+if __name__ == '__main__':
+ ring = get_keyring()
+
+ print ring.is_available()
+
+ print "storing"
+ ring.store_login(kind='google', username='havoc pennington+foo gmail com',
+ url='http://google.com/', password='frob')
+
+ print "getting"
+ print ring.get_logins(kind='google', username='havoc pennington+foo gmail com',
+ url='http://google.com/')
+
+ print "done"
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]