+from oauth import oauth
+import gnome.gconf
+import gobject
+import gtk
+import hildon
+import org.maemo.hermes.engine.provider
+import time
+import thread
+import httplib
+import re
+import webbrowser
+
+class OAuthProvider(org.maemo.hermes.engine.provider.Provider):
+ """Basis for OAuth services for Hermes. Sub-classes of this should
+ install keys to '<id>_...', and implement the following
+ methods:
+
+ * get_name
+ * get_urls (tuple of request token URL, access token URL & authorize URL)
+ * verify_verifier (returns name of authenticated user)
+ * additional_prefs [optional]
+ * handle_prefs_button [optional]
+
+ Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
+ Released under the Artistic Licence."""
+
+ GCONF_API_KEY = '/apps/maemo/hermes/%s_key'
+ GCONF_API_SECRET = '/apps/maemo/hermes/%s_secret'
+ GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/%s_access_token'
+ GCONF_USER = '/apps/maemo/hermes/%s_user'
+
+ # -----------------------------------------------------------------------
+ def __init__(self):
+ """Initialise the provider, and ensure the environment is going to work."""
+
+ self._gc = gnome.gconf.client_get_default()
+
+ api_key = self._gc.get_string(self.GCONF_API_KEY % (self.get_id()))
+ secret_key = self._gc.get_string(self.GCONF_API_SECRET % (self.get_id()))
+ if api_key is None or secret_key is None:
+ raise Exception('No application keys found for %s. Installation error.' % (self.get_id()))
+
+ self.access_token = self._get_access_token_from_gconf()
+ self.consumer = oauth.OAuthConsumer(api_key, secret_key)
+ self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+
+
+ # -----------------------------------------------------------------------
+ def get_urls(self):
+ """Return a tuple containing request token, access token & authorize URLs."""
+
+ return (None, None, None)
+
+
+ # -----------------------------------------------------------------------
+ def get_account_detail(self):
+ """Return the name of the linked LinkedIn account."""
+
+ return self._gc.get_string(self.GCONF_USER % (self.get_id()))
+
+
+ # -----------------------------------------------------------------------
+ def has_preferences(self):
+ """Whether or not this provider has any preferences. If it does not,
+ open_preferences must NOT be called; as the behaviour is undetermined."""
+
+ return True
+
+
+ # -----------------------------------------------------------------------
+ def open_preferences(self, parent):
+ """Open the preferences for this provider as a child of the 'parent' widget."""
+
+ self.main_window = parent
+ dialog = gtk.Dialog(self.get_name(), parent)
+ dialog.add_button(_('Disable'), gtk.RESPONSE_NO)
+ enable = dialog.add_button(_('Enable'), gtk.RESPONSE_YES)
+
+ button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ self._handle_button(None, button, enable)
+ button.connect('clicked', self._handle_button, button, enable)
+
+ dialog.vbox.add(button)
+ self.additional_prefs(dialog)
+ dialog.vbox.add(gtk.Label(""))
+
+ dialog.show_all()
+ result = dialog.run()
+ dialog.hide()
+
+ self.handle_prefs_response(result)
+ if result == gtk.RESPONSE_CANCEL or result == gtk.RESPONSE_DELETE_EVENT:
+ return None
+
+ return result == gtk.RESPONSE_YES
+
+
+ # -----------------------------------------------------------------------
+ def additional_prefs(self, dialog):
+ """Override to add additional controls to the authentication dialogue."""
+
+ dialog.vbox.add(gtk.Label(""))
+ dialog.vbox.add(gtk.Label(""))
+
+
+ # -----------------------------------------------------------------------
+ def handle_prefs_response(self, result):
+ """Override to handle the response from a dialogue button."""
+
+ None
+
+
+ # -----------------------------------------------------------------------
+ def _handle_button(self, e, button, enable):
+ """Ensure the button state is correct."""
+
+ authenticated = self._get_access_token_from_gconf() is not None
+ if e is not None:
+ if authenticated:
+ self._remove_access_token_from_gconf()
+ else:
+ self.authenticate(lambda: None, self.block_for_auth)
+
+ authenticated = self._get_access_token_from_gconf() is not None
+
+ button.set_title(authenticated and _("Clear authorisation") or _("Authorise"))
+ enable.set_sensitive(authenticated)
+
+
+ # -----------------------------------------------------------------------
+ def block_for_auth(self, url):
+ """Part of the GUI callback API."""
+
+ webbrowser.open(url)
+ time.sleep(3)
+ note = gtk.Dialog(_('Service authorisation'), self.main_window)
+ note.add_button(_("Validate"), gtk.RESPONSE_OK)
+ input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
+ input.set_property('is-focus', False)
+ input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_NUMERIC)
+ note.set_title(_("Verification code from web browser"))
+ note.vbox.add(input)
+
+ note.show_all()
+ result = note.run()
+ note.hide()
+ if result == gtk.RESPONSE_OK:
+ return input.get_text()
+ else:
+ return None
+
+
+ # -----------------------------------------------------------------------
+ def authenticate(self, need_auth, block_for_auth):
+ need_auth()
+ token = self._get_request_token()
+ url = self._get_authorize_url(token)
+ verifier = block_for_auth(url)
+ self.access_token = self._get_access_token(token, verifier)
+ name = self.verify_verifier(self.access_token)
+ self._gc.set_string(self.GCONF_USER % (self.get_id()), name)
+
+
+ # -----------------------------------------------------------------------
+ def _store_access_token_in_gconf(self, token_str):
+ if "oauth_problem" in token_str:
+ raise Exception("Authorization failure - access token reported OAuth problem")
+
+ self._gc.set_string(self.GCONF_ACCESS_TOKEN % (self.get_id()), token_str)
+
+
+ # -----------------------------------------------------------------------
+ def _get_access_token_from_gconf(self):
+ """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
+
+ token_str = self._gc.get_string(self.GCONF_ACCESS_TOKEN % (self.get_id()))
+ print token_str
+ if not token_str or len(token_str) < 8:
+ return None
+ return oauth.OAuthToken.from_string(token_str)
+
+
+ # -----------------------------------------------------------------------
+ def _remove_access_token_from_gconf(self):
+ """Remove the oauth.OAuthToken, if any."""
+
+ self._gc.unset(self.GCONF_ACCESS_TOKEN % (self.get_id()))
+ self._gc.unset(self.GCONF_USER % (self.get_id()))
+
+
+ # -----------------------------------------------------------------------
+ def _get_request_token(self):
+ """Get a request token from OAuth provider."""
+
+ url = self.get_urls()[0]
+ hostname = self._get_hostname_from_url(url)
+
+ oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=url)
+ oauth_request.sign_request(self.sig_method, self.consumer, None)
+
+ connection = httplib.HTTPSConnection(hostname)
+ connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
+ response = connection.getresponse().read()
+
+ try:
+ token = oauth.OAuthToken.from_string(response)
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ print response
+ raise Exception("Authorization failure - failed to get request token")
+ return token
+
+
+ # -----------------------------------------------------------------------
+ def _get_authorize_url(self, token):
+ """The URL that the user should browse to, in order to authorize the
+ application's request to access data"""
+
+ oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.get_urls()[2])
+ return oauth_request.to_url()
+
+
+ # -----------------------------------------------------------------------
+ def _get_access_token(self, token, verifier):
+ """If the verifier (which was displayed in the browser window) is
+ valid, then an access token is returned which should be used to
+ access data on the service."""
+
+ url = self.get_urls()[1]
+ hostname = self._get_hostname_from_url(url)
+
+ oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=url)
+ oauth_request.sign_request(self.sig_method, self.consumer, token)
+
+ connection = httplib.HTTPSConnection(hostname)
+ connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
+ response = connection.getresponse()
+ token_str = response.read()
+ if 'oauth_problem' in token_str:
+ raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
+ self._store_access_token_in_gconf(token_str)
+ return oauth.OAuthToken.from_string(token_str)
+
+
+ # -----------------------------------------------------------------------
+ def make_api_request(self, url):
+ hostname = self._get_hostname_from_url(url)
+
+ oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
+ oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
+ connection = httplib.HTTPSConnection(hostname)
+ try:
+ connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
+ xml = connection.getresponse().read()
+ return xml
+ except:
+ raise Exception("Failed to contact LinkedIn at " + url)
+
+
+ # -----------------------------------------------------------------------
+ def _get_hostname_from_url(self, url):
+ """Extract the hostname from a URL."""
+ return re.sub(r'^\w+://(.+?)[/:].*$', r'\1', url)
+
\ No newline at end of file