1 from oauth import oauth
6 import org.maemo.hermes.engine.provider
13 class OAuthProvider(org.maemo.hermes.engine.provider.Provider):
14 """Basis for OAuth services for Hermes. Sub-classes of this should
15 install keys to '<id>_...', and implement the following
19 * get_urls (tuple of request token URL, access token URL & authorize URL)
20 * verify_verifier (returns name of authenticated user)
21 * additional_prefs [optional]
22 * handle_prefs_button [optional]
24 Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
25 Released under the Artistic Licence."""
27 GCONF_API_KEY = '/apps/maemo/hermes/%s_key'
28 GCONF_API_SECRET = '/apps/maemo/hermes/%s_secret'
29 GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/%s_access_token'
30 GCONF_USER = '/apps/maemo/hermes/%s_user'
32 # -----------------------------------------------------------------------
34 """Initialise the provider, and ensure the environment is going to work."""
36 self._gc = gnome.gconf.client_get_default()
38 api_key = self._gc.get_string(self.GCONF_API_KEY % (self.get_id()))
39 secret_key = self._gc.get_string(self.GCONF_API_SECRET % (self.get_id()))
40 if api_key is None or secret_key is None:
41 raise Exception('No application keys found for %s. Installation error.' % (self.get_id()))
43 self.access_token = self._get_access_token_from_gconf()
44 self.consumer = oauth.OAuthConsumer(api_key, secret_key)
45 self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
48 # -----------------------------------------------------------------------
50 """Return a tuple containing request token, access token & authorize URLs."""
52 return (None, None, None)
55 # -----------------------------------------------------------------------
56 def get_account_detail(self):
57 """Return the name of the linked LinkedIn account."""
59 return self._gc.get_string(self.GCONF_USER % (self.get_id()))
62 # -----------------------------------------------------------------------
63 def has_preferences(self):
64 """Whether or not this provider has any preferences. If it does not,
65 open_preferences must NOT be called; as the behaviour is undetermined."""
70 # -----------------------------------------------------------------------
71 def open_preferences(self, parent):
72 """Open the preferences for this provider as a child of the 'parent' widget."""
74 self.main_window = parent
75 dialog = gtk.Dialog(self.get_name(), parent)
76 dialog.add_button(_('Disable'), gtk.RESPONSE_NO)
77 enable = dialog.add_button(_('Enable'), gtk.RESPONSE_YES)
79 button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT,
80 hildon.BUTTON_ARRANGEMENT_VERTICAL)
81 self._handle_button(None, button, enable)
82 button.connect('clicked', self._handle_button, button, enable)
84 dialog.vbox.add(button)
85 self.additional_prefs(dialog)
86 dialog.vbox.add(gtk.Label(""))
92 if result == gtk.RESPONSE_CANCEL or result == gtk.RESPONSE_DELETE_EVENT:
95 self.handle_prefs_response(result)
96 return result == gtk.RESPONSE_YES
99 # -----------------------------------------------------------------------
100 def additional_prefs(self, dialog):
101 """Override to add additional controls to the authentication dialogue."""
103 dialog.vbox.add(gtk.Label(""))
104 dialog.vbox.add(gtk.Label(""))
107 # -----------------------------------------------------------------------
108 def handle_prefs_response(self, result):
109 """Override to handle the response from a dialogue button."""
114 # -----------------------------------------------------------------------
115 def _handle_button(self, e, button, enable):
116 """Ensure the button state is correct."""
118 authenticated = self._get_access_token_from_gconf() is not None
121 self._remove_access_token_from_gconf()
123 self.authenticate(lambda: None, self.block_for_auth)
125 authenticated = self._get_access_token_from_gconf() is not None
127 button.set_title(authenticated and _("Clear authorisation") or _("Authorise"))
128 enable.set_sensitive(authenticated)
131 # -----------------------------------------------------------------------
132 def block_for_auth(self, url):
133 """Part of the GUI callback API."""
137 note = gtk.Dialog(_('Service authorisation'), self.main_window)
138 note.add_button(_("Validate"), gtk.RESPONSE_OK)
139 input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
140 input.set_property('is-focus', False)
141 input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_NUMERIC)
142 note.set_title(_("Verification code from web browser"))
148 if result == gtk.RESPONSE_OK:
149 return input.get_text()
154 # -----------------------------------------------------------------------
155 def authenticate(self, need_auth, block_for_auth):
157 token = self._get_request_token()
158 url = self._get_authorize_url(token)
159 verifier = block_for_auth(url)
160 self.access_token = self._get_access_token(token, verifier)
161 name = self.verify_verifier(self.access_token)
162 self._gc.set_string(self.GCONF_USER % (self.get_id()), name)
165 # -----------------------------------------------------------------------
166 def _store_access_token_in_gconf(self, token_str):
167 if "oauth_problem" in token_str:
168 raise Exception("Authorization failure - access token reported OAuth problem")
170 self._gc.set_string(self.GCONF_ACCESS_TOKEN % (self.get_id()), token_str)
173 # -----------------------------------------------------------------------
174 def _get_access_token_from_gconf(self):
175 """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
177 token_str = self._gc.get_string(self.GCONF_ACCESS_TOKEN % (self.get_id()))
178 if not token_str or len(token_str) < 8:
181 return oauth.OAuthToken.from_string(token_str)
183 print "Invalid: ", token_str
187 # -----------------------------------------------------------------------
188 def _remove_access_token_from_gconf(self):
189 """Remove the oauth.OAuthToken, if any."""
191 self._gc.unset(self.GCONF_ACCESS_TOKEN % (self.get_id()))
192 self._gc.unset(self.GCONF_USER % (self.get_id()))
195 # -----------------------------------------------------------------------
196 def _get_request_token(self):
197 """Get a request token from OAuth provider."""
199 url = self.get_urls()[0]
200 hostname = self._get_hostname_from_url(url)
202 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=url)
203 oauth_request.sign_request(self.sig_method, self.consumer, None)
205 connection = httplib.HTTPSConnection(hostname)
206 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
207 response = connection.getresponse().read()
210 token = oauth.OAuthToken.from_string(response)
213 traceback.print_exc()
215 raise Exception("Authorization failure - failed to get request token")
219 # -----------------------------------------------------------------------
220 def _get_authorize_url(self, token):
221 """The URL that the user should browse to, in order to authorize the
222 application's request to access data"""
224 oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.get_urls()[2])
225 return oauth_request.to_url()
228 # -----------------------------------------------------------------------
229 def _get_access_token(self, token, verifier):
230 """If the verifier (which was displayed in the browser window) is
231 valid, then an access token is returned which should be used to
232 access data on the service."""
234 url = self.get_urls()[1]
235 hostname = self._get_hostname_from_url(url)
237 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=url)
238 oauth_request.sign_request(self.sig_method, self.consumer, token)
240 connection = httplib.HTTPSConnection(hostname)
241 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
242 response = connection.getresponse()
243 token_str = response.read()
244 if 'oauth_problem' in token_str:
245 raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
246 self._store_access_token_in_gconf(token_str)
247 return oauth.OAuthToken.from_string(token_str)
250 # -----------------------------------------------------------------------
251 def make_api_request(self, url):
252 hostname = self._get_hostname_from_url(url)
254 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
255 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
256 connection = httplib.HTTPSConnection(hostname)
258 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
259 xml = connection.getresponse().read()
262 raise Exception("Failed to contact LinkedIn at " + url)
265 # -----------------------------------------------------------------------
266 def _get_hostname_from_url(self, url):
267 """Extract the hostname from a URL."""
268 return re.sub(r'^\w+://(.+?)[/:].*$', r'\1', url)