Merge lp:~alecu/ubuntu-sso-client/credentials-interface into lp:ubuntu-sso-client

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Rodrigo Moya
Approved revision: 565
Merged at revision: 559
Proposed branch: lp:~alecu/ubuntu-sso-client/credentials-interface
Merge into: lp:ubuntu-sso-client
Diff against target: 577 lines (+340/-36)
6 files modified
bin/ubuntu-sso-login (+7/-2)
ubuntu_sso/__init__.py (+1/-0)
ubuntu_sso/keyring.py (+111/-0)
ubuntu_sso/main.py (+74/-18)
ubuntu_sso/tests/test_gui.py (+0/-10)
ubuntu_sso/tests/test_main.py (+147/-6)
To merge this branch: bzr merge lp:~alecu/ubuntu-sso-client/credentials-interface
Reviewer Review Type Date Requested Status
Rodrigo Moya (community) Approve
Natalia Bidart (community) Approve
Review via email: mp+31896@code.launchpad.net

Commit message

DBus interface for getting the credentials from the keyring, or show a dialog to login/register

Description of the change

DBus interface for getting the credentials from the keyring, or show a dialog to login/register

To post a comment you must log in.
564. By Alejandro J. Cura

nessita's eagle eye spotted this pring

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

keyring.py needs tests but they are coming on a different branch.

review: Approve
565. By Alejandro J. Cura

do not store the keyring tokens yet

Revision history for this message
Rodrigo Moya (rodrigo-moya) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/ubuntu-sso-login'
2--- bin/ubuntu-sso-login 2010-08-03 20:03:44 +0000
3+++ bin/ubuntu-sso-login 2010-08-06 02:49:40 +0000
4@@ -30,12 +30,13 @@
5
6 from dbus.mainloop.glib import DBusGMainLoop
7
8-from ubuntu_sso import DBUS_IFACE_AUTH_NAME, DBUS_BUS_NAME
9-from ubuntu_sso.main import Login, SSOLogin
10+from ubuntu_sso import DBUS_IFACE_AUTH_NAME, DBUS_BUS_NAME, DBUS_CRED_PATH
11+from ubuntu_sso.main import Login, SSOLogin, SSOCredentials
12
13 from ubuntu_sso.logger import setupLogging
14 logger = setupLogging("ubuntu-sso-login")
15
16+gtk.gdk.threads_init()
17 DBusGMainLoop(set_as_default=True)
18
19 _ = gettext.gettext
20@@ -235,5 +236,9 @@
21 bus=dbus.SessionBus()))
22 ssoLogin = SSOLogin(dbus.service.BusName(DBUS_BUS_NAME,
23 bus=dbus.SessionBus()))
24+ SSOCredentials(dbus.service.BusName(DBUS_BUS_NAME,
25+ bus=dbus.SessionBus()),
26+ object_path=DBUS_CRED_PATH)
27+
28 manager = LoginMain()
29 manager.main()
30
31=== modified file 'ubuntu_sso/__init__.py'
32--- ubuntu_sso/__init__.py 2010-08-03 15:02:15 +0000
33+++ ubuntu_sso/__init__.py 2010-08-06 02:49:40 +0000
34@@ -19,6 +19,7 @@
35 DBUS_PATH_AUTH = "/"
36 DBUS_BUS_NAME = "com.ubuntu.sso"
37 DBUS_PATH = "/sso"
38+DBUS_CRED_PATH = "/credentials"
39 DBUS_IFACE_AUTH_NAME = "com.ubuntu.sso"
40 DBUS_IFACE_USER_NAME = "com.ubuntu.sso.UserManagement"
41 DBUS_IFACE_CRED_NAME = "com.ubuntu.sso.ApplicationCredentials"
42
43=== added file 'ubuntu_sso/keyring.py'
44--- ubuntu_sso/keyring.py 1970-01-01 00:00:00 +0000
45+++ ubuntu_sso/keyring.py 2010-08-06 02:49:40 +0000
46@@ -0,0 +1,111 @@
47+# Copyright (C) 2010 Canonical
48+#
49+# Authors:
50+# Andrew Higginson
51+#
52+# This program is free software; you can redistribute it and/or modify it under
53+# the terms of the GNU General Public License as published by the Free Software
54+# Foundation; version 3.
55+#
56+# This program is distributed in the hope that it will be useful, but WITHOUT
57+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
58+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
59+# details.
60+#
61+# You should have received a copy of the GNU General Public License along with
62+# this program; if not, write to the Free Software Foundation, Inc.,
63+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
64+
65+import gnomekeyring
66+
67+from urllib import urlencode
68+from urlparse import parse_qs
69+
70+class Keyring(object):
71+
72+ KEYRING_NAME = "login"
73+
74+ def __init__(self, app_name):
75+
76+ if not gnomekeyring.is_available():
77+ raise gnomekeyring.NoKeyringDaemonError
78+ self.app_name = app_name
79+
80+ def _create_keyring(self, name):
81+ """
82+ Creates a keyring, if it already exists,
83+ it does nothing
84+ """
85+ keyring_names = gnomekeyring.list_keyring_names_sync()
86+ if not name in keyring_names:
87+ gnomekeyring.create_sync(name)
88+
89+ def _item_exists(self, sync, name):
90+ """
91+ Returns whether a named item exists in a
92+ named keyring
93+ """
94+ return self._get_item_id_from_name(sync, name) != None
95+
96+ def _get_item_id_from_name(self, sync, name):
97+ """
98+ Returns the ID for a named item
99+ """
100+ for item_id in gnomekeyring.list_item_ids_sync(sync):
101+ item_info = gnomekeyring.item_get_info_sync(sync, item_id)
102+ display_name = item_info.get_display_name()
103+ if display_name == name:
104+ return item_id
105+ return None
106+
107+ def _get_item_info_from_name(self, sync, name):
108+ """
109+ Returns the ID for a named item
110+ """
111+ for item_id in gnomekeyring.list_item_ids_sync(sync):
112+ item_info = gnomekeyring.item_get_info_sync(sync, item_id)
113+ display_name = item_info.get_display_name()
114+ if display_name == name:
115+ return item_info
116+ return None
117+
118+ def set_ubuntusso_attr(self, attr):
119+ """
120+ Sets the attributes of the Ubuntu SSO item
121+ """
122+ # Creates the secret from the attributes
123+ secret = urlencode(attr)
124+
125+ # Create the keyring
126+ self._create_keyring(self.KEYRING_NAME)
127+
128+ # If the item already exists, delete it
129+ if self._item_exists(self.KEYRING_NAME, self.app_name):
130+ item_id = self._get_item_id_from_name(self.KEYRING_NAME,
131+ self.app_name)
132+ gnomekeyring.item_delete_sync(self.KEYRING_NAME, item_id)
133+
134+ # Add our SSO item
135+ gnomekeyring.item_create_sync(self.KEYRING_NAME,
136+ gnomekeyring.ITEM_GENERIC_SECRET, self.app_name, {},
137+ secret, True)
138+
139+ def get_ubuntusso_attr(self):
140+ """
141+ Returns the secret of the SSO item in a dictionary
142+ """
143+ # If we have no attributes, return None
144+ if self._get_item_info_from_name(self.KEYRING_NAME,
145+ self.app_name) == None:
146+ return None
147+ secret = self._get_item_info_from_name(self.KEYRING_NAME,
148+ self.app_name).get_secret()
149+ return parse_qs(secret)
150+
151+if __name__ == "__main__":
152+ SSO_ITEM_NAME = "Software Center UbuntuSSO token"
153+ keyring = Keyring(SSO_ITEM_NAME)
154+ keyring.set_ubuntusso_attr({"ha":"hehddddeff", "hi":"hggehes", "ho":"he"})
155+ print keyring.get_ubuntusso_attr()
156+
157+
158
159=== modified file 'ubuntu_sso/main.py'
160--- ubuntu_sso/main.py 2010-08-05 02:24:33 +0000
161+++ ubuntu_sso/main.py 2010-08-06 02:49:40 +0000
162@@ -34,6 +34,7 @@
163
164 import dbus.service
165 import gobject
166+import gtk
167 import pynotify
168
169 from dbus.mainloop.glib import DBusGMainLoop
170@@ -43,20 +44,18 @@
171 from lazr.restfulclient.resource import ServiceRoot
172 from oauth.oauth import OAuthToken
173
174+from keyring import Keyring
175 from ubuntu_sso import (DBUS_IFACE_AUTH_NAME,
176 DBUS_IFACE_USER_NAME,
177 DBUS_IFACE_CRED_NAME,
178+ DBUS_CRED_PATH,
179 DBUS_BUS_NAME)
180 from ubuntu_sso.config import get_config
181
182 from ubuntu_sso.logger import setupLogging
183 logger = setupLogging("ubuntu_sso.main")
184
185-
186 DBusGMainLoop(set_as_default=True)
187-dbus.mainloop.glib.threads_init()
188-gobject.threads_init()
189-
190 # Disable the invalid name warning, as we have a lot of DBus style names
191 # pylint: disable-msg=C0103
192
193@@ -92,6 +91,21 @@
194 """The email token is not valid."""
195
196
197+def keyring_store_token(token_name, token):
198+ """Store the credentials in the keyring."""
199+ attr = {"token": token}
200+ Keyring(token_name).set_ubuntusso_attr(attr)
201+
202+
203+def keyring_get_token(token_name):
204+ """Get the credentials from the keyring or None if not there."""
205+ d = Keyring(token_name).get_ubuntusso_attr()
206+ if d is not None:
207+ return d["token"]
208+ else:
209+ return None
210+
211+
212 class SSOLoginProcessor(object):
213 """Login and register users using the Ubuntu Single Sign On service."""
214
215@@ -208,7 +222,7 @@
216 try:
217 result_cb(f())
218 except Exception, e:
219- logger.error(e)
220+ logger.exception(e)
221 error_cb(str(e))
222 threading.Thread(target=_in_thread).start()
223
224@@ -230,9 +244,6 @@
225 return self.sso_login_processor_class(
226 sso_service_class=self.sso_service_class)
227
228- # ========================================
229- # DBUS_IFACE_USER_NAME methods and signals
230-
231 # generate_capcha signals
232 @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="s")
233 def CaptchaGenerated(self, result):
234@@ -285,7 +296,10 @@
235 """Call the matching method in the processor."""
236 def f():
237 """Inner function that will be run in a thread."""
238- return self.processor().login(email, password, token_name)
239+ token = self.processor().login(email, password, token_name)
240+ # XXX: will be fixed in upcoming branch
241+ #keyring_store_token(token_name, token)
242+ return "Ok"
243 return blocking(f, self.LoggedIn, self.LoginError)
244
245 # validate_email signals
246@@ -307,8 +321,9 @@
247 token_name, email_token)
248 return blocking(f, self.EmailValidated, self.EmailValidationError)
249
250- # ----------------------------------------
251- # DBUS_IFACE_CRED_NAME methods and signals
252+
253+class SSOCredentials(dbus.service.Object):
254+ """DBus object that gets credentials, and login/registers if needed."""
255
256 @dbus.service.signal(DBUS_IFACE_CRED_NAME, signature="s")
257 def CredentialsFound(self, result):
258@@ -319,17 +334,53 @@
259 """Signal thrown when there is a problem finding the credentials."""
260
261 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
262- in_signature="s", out_signature="")
263- def find_credentials(self, application_name):
264- # XXX: placeholder; coming in the following branch
265- pass
266+ in_signature="s", out_signature="s")
267+ def find_credentials(self, token_name):
268+ """Get the credentials from the keyring or '' if not there."""
269+ token = keyring_get_token(token_name)
270+ if token is None:
271+ return ""
272+ else:
273+ return token
274+
275+ def _response_cb(self, dialog, response, token_name):
276+ """Handles the response from the UI dialog."""
277+ # XXX: to be replaced in the following branch
278+ try:
279+ token = "token%d" % response
280+ keyring_store_token(token_name, token)
281+ self.CredentialsFound(token)
282+ except Exception, e:
283+ self.CredentialsError(str(e))
284+ dialog.destroy()
285+
286+ def _show_login_or_register_ui(self, token_name, terms_and_conditions_url):
287+ """Shows the UI so the user can login or register."""
288+ # XXX: to be replaced in the following branch
289+ try:
290+ dialog = gtk.MessageDialog(None,
291+ gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO,
292+ gtk.BUTTONS_CLOSE, terms_and_conditions_url)
293+ dialog.connect("response", self._response_cb, token_name)
294+ dialog.show()
295+ except Exception, e:
296+ self.CredentialsError(str(e))
297
298 @dbus.service.method(dbus_interface=DBUS_IFACE_CRED_NAME,
299 in_signature="ss", out_signature="")
300- def login_or_register_to_get_credentials(self, application_name,
301+ def login_or_register_to_get_credentials(self, token_name,
302 terms_and_conditions_url):
303- # XXX: placeholder; coming in the following branch
304- pass
305+
306+ """Get credentials if found else prompt to login or register first."""
307+ try:
308+ token = keyring_get_token(token_name)
309+ if token is None:
310+ gobject.idle_add(self._show_login_or_register_ui, token_name,
311+ terms_and_conditions_url)
312+ else:
313+ self.CredentialsFound(token)
314+ except Exception, e:
315+ self.CredentialsError(str(e))
316
317
318 class LoginProcessor:
319@@ -558,6 +609,8 @@
320
321 def main():
322 """Start everything"""
323+ dbus.mainloop.glib.threads_init()
324+ gobject.threads_init()
325 logger.debug("Starting up at %s", time.asctime())
326 logger.debug("Installing the Twisted glib2reactor")
327 from twisted.internet import glib2reactor # for non-GUI apps
328@@ -569,6 +622,9 @@
329 bus=dbus.SessionBus()))
330 SSOLogin(dbus.service.BusName(DBUS_BUS_NAME,
331 bus=dbus.SessionBus()))
332+ SSOCredentials(dbus.service.BusName(DBUS_BUS_NAME,
333+ bus=dbus.SessionBus()),
334+ object_path=DBUS_CRED_PATH)
335 # cleverness here to say:
336 # am I already running (bound to this d-bus name)?
337 # if so, send a signal to the already running instance
338
339=== modified file 'ubuntu_sso/tests/test_gui.py'
340--- ubuntu_sso/tests/test_gui.py 2010-08-05 02:24:33 +0000
341+++ ubuntu_sso/tests/test_gui.py 2010-08-06 02:49:40 +0000
342@@ -46,12 +46,6 @@
343 EMAIL_TOKEN = 'B2Pgtf'
344
345
346-def process_pending():
347- """Process all the pending GTK events."""
348- while gtk.events_pending():
349- gtk.main_iteration()
350-
351-
352 class FakedSSOBackend(object):
353 """Fake a SSO Backend (acts as a dbus.Interface as well)."""
354
355@@ -149,7 +143,6 @@
356 """Grab focus on widget, if None use self.entry."""
357 direction = 'in' if focus_in else 'out'
358 self.entry.emit('focus-%s-event' % direction, None)
359- process_pending()
360
361 def assert_correct_label(self):
362 """Check that the entry has the correct label."""
363@@ -212,7 +205,6 @@
364 self.grab_focus() # grab focus
365
366 self.entry.set_text(' ') # add empty text to the entry
367- process_pending()
368
369 self.grab_focus(focus_in=False) # loose focus
370
371@@ -227,7 +219,6 @@
372 self.grab_focus() # grab focus
373
374 self.entry.set_text(expected) # add empty text to the entry
375- process_pending()
376
377 self.grab_focus(focus_in=False) # loose focus
378
379@@ -241,7 +232,6 @@
380 self.grab_focus() # grab focus
381
382 self.entry.set_text(expected) # add text to the entry
383- process_pending()
384
385 self.grab_focus(focus_in=False) # loose focus
386 self.grab_focus() # grab focus again!
387
388=== modified file 'ubuntu_sso/tests/test_main.py'
389--- ubuntu_sso/tests/test_main.py 2010-08-04 00:10:14 +0000
390+++ ubuntu_sso/tests/test_main.py 2010-08-06 02:49:40 +0000
391@@ -31,7 +31,8 @@
392 from ubuntu_sso import config
393 from ubuntu_sso.main import (
394 AuthenticationError, BadRealmError, blocking, EmailTokenError,
395- InvalidEmailError, InvalidPasswordError, SSOLogin,
396+ InvalidEmailError, InvalidPasswordError, SSOLogin, SSOCredentials,
397+ keyring_get_token, keyring_store_token,
398 LoginProcessor, SSOLoginProcessor, RegistrationError)
399
400
401@@ -110,7 +111,7 @@
402 self.accounts = FakedAccounts()
403
404
405-class SSOLoginProcessorTestCase(TestCase):
406+class SSOLoginProcessorTestCase(TestCase, MockerTestCase):
407 """Test suite for the SSO login processor."""
408
409 def setUp(self):
410@@ -436,14 +437,14 @@
411 email = "sample email"
412 password = "sample password"
413 token_name = "sample token_name"
414- expected_result = "expected result"
415+ sample_result = {"sample": "tokens"}
416 self.create_mock_processor().login(email, password, token_name)
417- self.mocker.result(expected_result)
418+ self.mocker.result(sample_result)
419 self.patch(ubuntu_sso.main, "blocking", self.fake_ok_blocking)
420 self.mocker.replay()
421
422 def verify(result):
423- self.assertEqual(result, expected_result)
424+ self.assertEqual(result, "Ok")
425 d.callback(result)
426
427 client = SSOLogin(self.mockbusname,
428@@ -556,8 +557,148 @@
429 raise BlockingSampleException(expected_error_message)
430
431 def verify(error):
432- self.assertEquals(error, expected_error_message)
433+ self.assertEqual(error, expected_error_message)
434 d.callback("Ok")
435
436 blocking(f, d.errback, verify)
437 return d
438+
439+
440+class KeyringTokenTestCase(MockerTestCase):
441+ """Checks the functions that access the keyring."""
442+
443+ def test_keyring_store_token(self):
444+ """Verify the method that stores tokens."""
445+ token_name = "token_name"
446+ token_value = "token value"
447+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
448+ mockKeyringClass(token_name)
449+ mockKeyring = self.mocker.mock()
450+ self.mocker.result(mockKeyring)
451+ mockKeyring.set_ubuntusso_attr({"token": token_value})
452+ self.mocker.replay()
453+
454+ keyring_store_token(token_name, token_value)
455+
456+ def test_keyring_get_token(self):
457+ """The method returns the right token."""
458+ token_name = "token_name"
459+ token_value = "token value"
460+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
461+ mockKeyringClass(token_name)
462+ mockKeyring = self.mocker.mock()
463+ self.mocker.result(mockKeyring)
464+ mockKeyring.get_ubuntusso_attr()
465+ self.mocker.result({"token": token_value})
466+ self.mocker.replay()
467+
468+ token = keyring_get_token(token_name)
469+ self.assertEqual(token, token_value)
470+
471+ def test_keyring_get_token_not_found(self):
472+ """The method returns None when the token is not found."""
473+ token_name = "token_name"
474+ mockKeyringClass = self.mocker.replace("ubuntu_sso.keyring.Keyring")
475+ mockKeyringClass(token_name)
476+ mockKeyring = self.mocker.mock()
477+ self.mocker.result(mockKeyring)
478+ mockKeyring.get_ubuntusso_attr()
479+ self.mocker.result(None)
480+ self.mocker.replay()
481+
482+ token = keyring_get_token(token_name)
483+ self.assertEqual(token, None)
484+
485+
486+class RegisterSampleException(Exception):
487+ """A mock exception thrown just when testing."""
488+
489+
490+class CredentialsTestCase(TestCase, MockerTestCase):
491+ """Tests for the credentials related DBus methods."""
492+
493+ def test_find_credentials(self):
494+ """find_credentials immediately returns the token when found."""
495+ expected_token = "expected token"
496+ kgt = self.mocker.replace("ubuntu_sso.main.keyring_get_token")
497+ kgt(ARGS)
498+ self.mocker.result(expected_token)
499+ self.mocker.replay()
500+
501+ client = SSOCredentials(self.mocker.mock())
502+ token = client.find_credentials("token_name")
503+ self.assertEqual(token, expected_token)
504+
505+ def test_credentials_not_found(self):
506+ """find_credentials immediately returns '' when no token found."""
507+ expected_token = ""
508+ kgt = self.mocker.replace("ubuntu_sso.main.keyring_get_token")
509+ kgt(ARGS)
510+ self.mocker.result(None)
511+ self.mocker.replay()
512+
513+ client = SSOCredentials(self.mocker.mock())
514+ token = client.find_credentials("token_name")
515+ self.assertEqual(token, expected_token)
516+
517+ def test_login_or_register(self):
518+ """login_or_register_... throws the signal when token is found."""
519+ expected_token = "expected token"
520+ d = Deferred()
521+
522+ def verify(result):
523+ self.assertEqual(result, expected_token)
524+ d.callback("ok")
525+
526+ kgt = self.mocker.replace("ubuntu_sso.main.keyring_get_token")
527+ kgt(ARGS)
528+ self.mocker.result(expected_token)
529+ self.mocker.replay()
530+ client = SSOCredentials(self.mocker.mock())
531+ self.patch(client, "_show_login_or_register_ui", self.fail)
532+ self.patch(client, "CredentialsFound", verify)
533+ self.patch(client, "CredentialsError", self.fail)
534+ client.login_or_register_to_get_credentials("token name", "tcurl")
535+ return d
536+
537+ def test_login_or_register_not_found(self):
538+ """Check that login_or_register_... opens the ui when no cred found."""
539+ token_name = "token name"
540+ d = Deferred()
541+
542+ def verify(result, *a):
543+ self.assertEqual(result, token_name)
544+ d.callback("ok")
545+
546+ kgt = self.mocker.replace("ubuntu_sso.main.keyring_get_token")
547+ kgt(ARGS)
548+ self.mocker.result(None)
549+ self.mocker.replay()
550+
551+ client = SSOCredentials(self.mocker.mock())
552+ self.patch(client, "_show_login_or_register_ui", verify)
553+ self.patch(client, "CredentialsFound", self.fail)
554+ self.patch(client, "CredentialsError", self.fail)
555+ client.login_or_register_to_get_credentials(token_name, "tcurl")
556+ return d
557+
558+ def test_login_or_register_problem(self):
559+ """login_or_register_... returns the right signal on error."""
560+ expected_error = "Sample Error - not for resale"
561+ d = Deferred()
562+
563+ def verify(result, *a):
564+ self.assertEqual(result, expected_error)
565+ d.callback("ok")
566+
567+ kgt = self.mocker.replace("ubuntu_sso.main.keyring_get_token")
568+ kgt(ARGS)
569+ self.mocker.throw(RegisterSampleException(expected_error))
570+ self.mocker.replay()
571+
572+ client = SSOCredentials(self.mocker.mock())
573+ self.patch(client, "_show_login_or_register_ui", self.fail)
574+ self.patch(client, "CredentialsFound", self.fail)
575+ self.patch(client, "CredentialsError", verify)
576+ client.login_or_register_to_get_credentials("token name", "tcurl")
577+ return d

Subscribers

People subscribed via source and target branches