Merge lp:~nataliabidart/ubuntuone-client/auth into lp:ubuntuone-client

Proposed by Natalia Bidart
Status: Merged
Approved by: Natalia Bidart
Approved revision: 784
Merged at revision: 789
Proposed branch: lp:~nataliabidart/ubuntuone-client/auth
Merge into: lp:ubuntuone-client
Diff against target: 944 lines (+866/-2)
9 files modified
Makefile.am (+3/-1)
bin/ubuntuone-login (+51/-0)
data/Makefile.am (+2/-1)
data/com.ubuntuone.Credentials.service.in (+4/-0)
po/POTFILES.in (+1/-0)
tests/credentials/__init__.py (+19/-0)
tests/credentials/test_credentials.py (+580/-0)
ubuntuone/clientdefs.py.in (+1/-0)
ubuntuone/credentials/__init__.py (+205/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-client/auth
Reviewer Review Type Date Requested Status
dobey (community) Approve
Guillermo Gonzalez Approve
Review via email: mp+45116@code.launchpad.net

Commit message

D-Bus service to provide Ubuntu One specific login capabilities (LP: #697211).

Description of the change

A new D-Bus service was added to provide Ubuntu One specific login capabilities.

The API is identical to the SSO client D-Bus service, except that no params has to be passed since this layer abstracts the caller from all the specific details.

To test, run from this branch:

DEBUG=True PYTHONPATH=. ./bin/ubuntuone-login

and play with the methods using d-feet, accessing the com.ubuntuone.Credentials service in the Session bus, /credentials object path, com.ubuntuone.CredentialsManagement interface.

To post a comment you must log in.
780. By Natalia Bidart

Adding installation specific bits.

Revision history for this message
dobey (dobey) wrote :

The translation bits are wrong here. The correct way is already done in clientdefs.py.in. You should never set the textdomain on import of a module, like is done here. I would put most of the code in the credentials module into the script itself; as there is no reason for any other application to provide the service, or do most of what is being done in the credentials module.

The tests in ubuntuone-client are also under the toplevel tests/ directory. So your tests aren't being run by 'make test' here.

And if you intend for the SSO defines in clientdefs.py to be deprecated, I would suggest changing them to lambdas that emit a DeprecationWarning, and return the correctly imported values instead.

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

> The translation bits are wrong here. The correct way is already done in
> clientdefs.py.in. You should never set the textdomain on import of a module,
> like is done here. I would put most of the code in the credentials module into
> the script itself; as there is no reason for any other application to provide
> the service, or do most of what is being done in the credentials module.

Putting the code inside the ubuntuone-login script makes testing almost impossible, so I should decline on doing that.

The textdomain thing is now removed and the Q_ function from clientdefs was copied and used.

> The tests in ubuntuone-client are also under the toplevel tests/ directory. So
> your tests aren't being run by 'make test' here.

Tests moved to tests/.

> And if you intend for the SSO defines in clientdefs.py to be deprecated, I
> would suggest changing them to lambdas that emit a DeprecationWarning, and
> return the correctly imported values instead.

I thought about this, but adding a lambda will make the constants be a function call, not a constant. So what I did was adding a module deprecation warning stating that "Constants APP_NAME, TC_URL, PING_URL and DESCRIPTION are deprecated."

Thanks for noticing this!

781. By Natalia Bidart

Credentials tests moved to tests/ folder.

782. By Natalia Bidart

Fixing review comments.

Revision history for this message
Guillermo Gonzalez (verterok) wrote :

looks good.

review: Approve
783. By Natalia Bidart

Auth constants are imported from ubuntuone.credentials.

Deprecation warning was removed to avoid having that when importing other
useful values.

784. By Natalia Bidart

Reverting changed to clientdefs to avoid circular import deps.

Revision history for this message
dobey (dobey) wrote :

I still strongly believe that the logging pieces and the CredentialsManagement class should be in the script itself, and only the constants themselves really need to be in the ubuntuone.credentials package. :-/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am'
2--- Makefile.am 2010-12-09 18:46:50 +0000
3+++ Makefile.am 2011-01-05 15:54:10 +0000
4@@ -9,6 +9,7 @@
5 # Python packages we want to install
6 pypackages = \
7 ubuntuone/api \
8+ ubuntuone/credentials \
9 ubuntuone/eventlog \
10 ubuntuone/platform \
11 ubuntuone/platform/linux \
12@@ -26,7 +27,8 @@
13 bin/u1sync
14
15 libexec_SCRIPTS = \
16- bin/ubuntuone-syncdaemon
17+ bin/ubuntuone-syncdaemon \
18+ bin/ubuntuone-login
19
20 clientdefsdir = $(pythondir)/ubuntuone
21 clientdefs_in_files = ubuntuone/clientdefs.py.in
22
23=== added file 'bin/ubuntuone-login'
24--- bin/ubuntuone-login 1970-01-01 00:00:00 +0000
25+++ bin/ubuntuone-login 2011-01-05 15:54:10 +0000
26@@ -0,0 +1,51 @@
27+#!/usr/bin/python
28+# -*- coding: utf-8 -*-
29+#
30+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
31+#
32+# Copyright 2010 Canonical Ltd.
33+#
34+# This program is free software: you can redistribute it and/or modify it
35+# under the terms of the GNU General Public License version 3, as published
36+# by the Free Software Foundation.
37+#
38+# This program is distributed in the hope that it will be useful, but
39+# WITHOUT ANY WARRANTY; without even the implied warranties of
40+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
41+# PURPOSE. See the GNU General Public License for more details.
42+#
43+# You should have received a copy of the GNU General Public License along
44+# with this program. If not, see <http://www.gnu.org/licenses/>.
45+
46+"""The script tu ron the Ubuntu One Login D-Bus service."""
47+
48+# Invalid name "ubuntuone-login"
49+# pylint: disable=C0103
50+
51+import sys
52+
53+import dbus.mainloop.glib
54+import dbus.service
55+import glib
56+
57+from ubuntuone.credentials import (DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH,
58+ CredentialsManagement, logger)
59+
60+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
61+
62+
63+if __name__ == "__main__":
64+ # Register DBus service for making sure we run only one instance
65+ bus = dbus.SessionBus()
66+ name = bus.request_name(DBUS_BUS_NAME,
67+ dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
68+ if name == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
69+ logger.error("Ubuntu One login manager already running, quitting.")
70+ sys.exit(0)
71+
72+ logger.info("Starting Ubuntu One login manager for bus %r.", DBUS_BUS_NAME)
73+ bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())
74+ CredentialsManagement(bus_name, object_path=DBUS_CREDENTIALS_PATH)
75+
76+ mainloop = glib.MainLoop()
77+ mainloop.run()
78
79=== modified file 'data/Makefile.am'
80--- data/Makefile.am 2010-06-16 21:55:54 +0000
81+++ data/Makefile.am 2011-01-05 15:54:10 +0000
82@@ -42,7 +42,8 @@
83
84 servicedir = $(DBUS_SERVICES_DIR)
85 service_in_files = \
86- com.ubuntuone.SyncDaemon.service.in
87+ com.ubuntuone.SyncDaemon.service.in \
88+ com.ubuntuone.Credentials.service.in
89 service_DATA = $(service_in_files:.service.in=.service)
90
91 %.service: %.service.in
92
93=== added file 'data/com.ubuntuone.Credentials.service.in'
94--- data/com.ubuntuone.Credentials.service.in 1970-01-01 00:00:00 +0000
95+++ data/com.ubuntuone.Credentials.service.in 2011-01-05 15:54:10 +0000
96@@ -0,0 +1,4 @@
97+[D-BUS Service]
98+Name=com.ubuntuone.Credentials
99+Exec=@libexecdir@/ubuntuone-login
100+
101
102=== modified file 'po/POTFILES.in'
103--- po/POTFILES.in 2010-10-06 07:42:51 +0000
104+++ po/POTFILES.in 2011-01-05 15:54:10 +0000
105@@ -1,5 +1,6 @@
106 bin/ubuntuone-preferences
107 ubuntuone/clientdefs.py.in
108+ubuntuone/credentials/__init__.py
109 data/emblem-ubuntuone-downloading.icon.in
110 data/emblem-ubuntuone-unsynchronized.icon.in
111 data/emblem-ubuntuone-uploading.icon.in
112
113=== added directory 'tests/credentials'
114=== added file 'tests/credentials/__init__.py'
115--- tests/credentials/__init__.py 1970-01-01 00:00:00 +0000
116+++ tests/credentials/__init__.py 2011-01-05 15:54:10 +0000
117@@ -0,0 +1,19 @@
118+# -*- coding: utf-8 -*-
119+#
120+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
121+#
122+# Copyright 2010 Canonical Ltd.
123+#
124+# This program is free software: you can redistribute it and/or modify it
125+# under the terms of the GNU General Public License version 3, as published
126+# by the Free Software Foundation.
127+#
128+# This program is distributed in the hope that it will be useful, but
129+# WITHOUT ANY WARRANTY; without even the implied warranties of
130+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
131+# PURPOSE. See the GNU General Public License for more details.
132+#
133+# You should have received a copy of the GNU General Public License along
134+# with this program. If not, see <http://www.gnu.org/licenses/>.
135+
136+"""Tests for the Ubuntu One credentials management dbus service."""
137
138=== added file 'tests/credentials/test_credentials.py'
139--- tests/credentials/test_credentials.py 1970-01-01 00:00:00 +0000
140+++ tests/credentials/test_credentials.py 2011-01-05 15:54:10 +0000
141@@ -0,0 +1,580 @@
142+# -*- coding: utf-8 -*-
143+#
144+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
145+#
146+# Copyright 2010 Canonical Ltd.
147+#
148+# This program is free software: you can redistribute it and/or modify it
149+# under the terms of the GNU General Public License version 3, as published
150+# by the Free Software Foundation.
151+#
152+# This program is distributed in the hope that it will be useful, but
153+# WITHOUT ANY WARRANTY; without even the implied warranties of
154+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
155+# PURPOSE. See the GNU General Public License for more details.
156+#
157+# You should have received a copy of the GNU General Public License along
158+# with this program. If not, see <http://www.gnu.org/licenses/>.
159+
160+"""Tests for the Ubuntu One credentials management dbus service."""
161+
162+from functools import wraps
163+
164+from twisted.internet.defer import Deferred, inlineCallbacks
165+from ubuntuone.devtools.testcase import DBusTestCase as TestCase
166+from ubuntuone.devtools.handlers import MementoHandler
167+
168+from ubuntuone.credentials import (dbus, logger, logging, ubuntu_sso,
169+ CredentialsManagement,
170+ DBUS_BUS_NAME, DBUS_CREDENTIALS_PATH, DBUS_CREDENTIALS_IFACE,
171+ APP_NAME, HELP_TEXT_KEY, DESCRIPTION, TC_URL_KEY, TC_URL,
172+ PING_URL_KEY, PING_URL, WINDOW_ID_KEY,
173+)
174+
175+FAKED_CREDENTIALS = {
176+ 'consumer_key': 'faked_consumer_key',
177+ 'consumer_secret': 'faked_consumer_secret',
178+ 'token': 'faked_token',
179+ 'token_secret': 'faked_token_secret',
180+ 'token_name': 'Woohoo test',
181+}
182+
183+
184+class FakedSSOService(dbus.service.Object):
185+ """Faked DBus object that manages credentials."""
186+
187+ error_dict = None
188+ app_name = None
189+
190+ def __init__(self, *args, **kwargs):
191+ super(FakedSSOService, self).__init__(*args, **kwargs)
192+ self._credentials = {}
193+ self._args = None
194+ self._kwargs = None
195+
196+ def maybe_emit_error(f):
197+ """Decorator to fake a CredentialsError signal."""
198+
199+ @wraps(f)
200+ def inner(self, *args, **kwargs):
201+ """Fake a CredentialsError signal."""
202+ if FakedSSOService.error_dict is not None:
203+ self.CredentialsError(FakedSSOService.app_name,
204+ FakedSSOService.error_dict)
205+ else:
206+ return f(self, *args, **kwargs)
207+
208+ return inner
209+
210+ def store_args(f):
211+ """Decorator to store arguments to check correct calls."""
212+
213+ @wraps(f)
214+ def inner(self, app_name, args):
215+ """Store arguments to check correct calls."""
216+ self._app_name = app_name
217+ self._args = args
218+ return f(self, app_name, args)
219+
220+ return inner
221+
222+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='s')
223+ def AuthorizationDenied(self, app_name):
224+ """Signal thrown when the user denies the authorization."""
225+
226+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
227+ def CredentialsFound(self, app_name, credentials):
228+ """Signal thrown when the credentials are found."""
229+
230+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='s')
231+ def CredentialsNotFound(self, app_name):
232+ """Signal thrown when the credentials are not found."""
233+
234+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='s')
235+ def CredentialsCleared(self, app_name):
236+ """Signal thrown when the credentials were cleared."""
237+
238+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='s')
239+ def CredentialsStored(self, app_name):
240+ """Signal thrown when the credentials were cleared."""
241+
242+ @dbus.service.signal(ubuntu_sso.DBUS_CREDENTIALS_IFACE, signature='sa{ss}')
243+ def CredentialsError(self, app_name, error_dict):
244+ """Signal thrown when there is a problem getting the credentials."""
245+
246+ @store_args
247+ @maybe_emit_error
248+ @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE,
249+ in_signature='sa{ss}', out_signature='')
250+ def find_credentials(self, app_name, args):
251+ """Look for the credentials for an application."""
252+ creds = self._credentials.get(FakedSSOService.app_name, None)
253+ if creds is not None:
254+ self.CredentialsFound(FakedSSOService.app_name, creds)
255+ else:
256+ self.CredentialsNotFound(FakedSSOService.app_name)
257+
258+ @store_args
259+ @maybe_emit_error
260+ @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE,
261+ in_signature='sa{ss}', out_signature='')
262+ def clear_credentials(self, app_name, args):
263+ """Clear the credentials for an application."""
264+ self._credentials.pop(FakedSSOService.app_name, None)
265+ self.CredentialsCleared(FakedSSOService.app_name)
266+
267+ @store_args
268+ @maybe_emit_error
269+ @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE,
270+ in_signature='sa{ss}', out_signature='')
271+ def store_credentials(self, app_name, args):
272+ """Store the token for an application."""
273+ self._credentials[FakedSSOService.app_name] = args
274+ self.CredentialsStored(FakedSSOService.app_name)
275+
276+ @store_args
277+ @maybe_emit_error
278+ @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE,
279+ in_signature='sa{ss}', out_signature='')
280+ def register(self, app_name, args):
281+ """Get credentials if found else prompt GUI to register."""
282+ creds = self._credentials.get(FakedSSOService.app_name, None)
283+ if creds is not None and len(creds) > 0:
284+ self.CredentialsFound(FakedSSOService.app_name, creds)
285+ elif creds == {}:
286+ # fake an AuthorizationDenied
287+ self.AuthorizationDenied(FakedSSOService.app_name)
288+ elif creds is None:
289+ # fake the adding of the credentials, in reality this will bring
290+ # a GUI that the user will interact with.
291+ self._credentials[FakedSSOService.app_name] = FAKED_CREDENTIALS
292+ self.CredentialsFound(FakedSSOService.app_name, FAKED_CREDENTIALS)
293+
294+ @store_args
295+ @maybe_emit_error
296+ @dbus.service.method(dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE,
297+ in_signature='sa{ss}', out_signature='')
298+ def login(self, app_name, args):
299+ """Get credentials if found else prompt GUI to login."""
300+ self.register(app_name, args)
301+
302+
303+class CredentialsManagementTestCase(TestCase):
304+ """Test case for the DBus object that manages Ubuntu One credentials."""
305+
306+ timeout = 2
307+ app_name = APP_NAME
308+ error_dict = None
309+ signals = ('CredentialsFound', 'CredentialsNotFound', 'CredentialsCleared',
310+ 'CredentialsStored', 'CredentialsError', 'AuthorizationDenied')
311+
312+ def setUp(self):
313+ super(CredentialsManagementTestCase, self).setUp()
314+ FakedSSOService.app_name = self.app_name
315+ FakedSSOService.error_dict = self.error_dict
316+
317+ self.memento = MementoHandler()
318+ self.memento.setLevel(logging.DEBUG)
319+ logger.addHandler(self.memento)
320+
321+ self.bus = dbus.SessionBus()
322+ self.sso_server = self.register_server(ubuntu_sso.DBUS_BUS_NAME,
323+ ubuntu_sso.DBUS_CREDENTIALS_PATH,
324+ FakedSSOService) # faked SSO server
325+ self.creds_server = self.register_server(DBUS_BUS_NAME,
326+ DBUS_CREDENTIALS_PATH,
327+ CredentialsManagement) # real service
328+
329+ self.deferred = Deferred()
330+ self.proxy = self.get_creds_proxy()
331+
332+ def register_server(self, bus_name, object_path, service_class):
333+ """Register a service on the session bus."""
334+ name = self.bus.request_name(bus_name, dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
335+ self.assertNotEqual(name, dbus.bus.REQUEST_NAME_REPLY_EXISTS,
336+ 'Service %s should not be running.' % bus_name)
337+ mock = service_class(object_path=object_path, conn=self.bus)
338+ self.addCleanup(mock.remove_from_connection)
339+
340+ return mock
341+
342+ def get_proxy(self, bus_name, object_path, dbus_interface):
343+ obj = self.bus.get_object(bus_name=bus_name, object_path=object_path,
344+ follow_name_owner_changes=True)
345+ proxy = dbus.Interface(object=obj, dbus_interface=dbus_interface)
346+ return proxy
347+
348+ def get_sso_proxy(self):
349+ return self.get_proxy(bus_name=ubuntu_sso.DBUS_BUS_NAME,
350+ object_path=ubuntu_sso.DBUS_CREDENTIALS_PATH,
351+ dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE)
352+
353+ def get_creds_proxy(self):
354+ return self.get_proxy(bus_name=DBUS_BUS_NAME,
355+ object_path=DBUS_CREDENTIALS_PATH,
356+ dbus_interface=DBUS_CREDENTIALS_IFACE)
357+
358+ def connect_signals(self, callback=None):
359+ """Connect every signal accordingly to fire self.deferred.
360+
361+ If 'callback' is not None, it will be used as a tuple (sig_name,
362+ function) and 'sig_name' will be connected to 'function', which should
363+ fire self.deferred properly.
364+
365+ """
366+ success_sig_name, success_function = None, None
367+ if callback is not None:
368+ success_sig_name, success_function = callback
369+
370+ def fail(sig_name):
371+ """Decorator to fire self.deferred."""
372+ def inner(*args, **kwargs):
373+ """Fire self.deferred."""
374+ msg = 'Received an unexpected signal (%r).' % sig_name
375+ self.deferred.errback(TypeError(msg))
376+ return inner
377+
378+ for sig_name in self.signals:
379+ if sig_name == success_sig_name:
380+ sig = self.proxy.connect_to_signal(sig_name, success_function)
381+ else:
382+ sig = self.proxy.connect_to_signal(sig_name, fail(sig_name))
383+ self.addCleanup(sig.remove)
384+
385+ @inlineCallbacks
386+ def add_credentials(self, creds=FAKED_CREDENTIALS):
387+ """Add some fake credentials for 'self.app_name'."""
388+ d = Deferred()
389+ sso_proxy = self.get_sso_proxy()
390+ sso_proxy.store_credentials(self.app_name, creds,
391+ reply_handler=lambda: d.callback(None),
392+ error_handler=d.errback)
393+ yield d
394+
395+ @inlineCallbacks
396+ def do_test(self):
397+ """Perform the test itself."""
398+ yield self.deferred
399+
400+ def test_get_sso_proxy(self):
401+ """The SSO dbus proxy is properly retrieved."""
402+ sso_proxy = CredentialsManagement().sso_proxy
403+ self.assertEqual(sso_proxy.bus_name, ubuntu_sso.DBUS_BUS_NAME)
404+ self.assertEqual(sso_proxy.object_path,
405+ ubuntu_sso.DBUS_CREDENTIALS_PATH)
406+ self.assertEqual(sso_proxy.dbus_interface,
407+ ubuntu_sso.DBUS_CREDENTIALS_IFACE)
408+
409+ @inlineCallbacks
410+ def test_shutdown(self):
411+ """On shutdown, SSO backend signals are disconnected."""
412+ d = Deferred()
413+
414+ self.proxy.shutdown(reply_handler=lambda: d.callback(None),
415+ error_handler=d.errback)
416+ yield d
417+
418+ # TODO: assert over params passed to remove_signal_receiver
419+
420+
421+class ArgsTestCase(CredentialsManagementTestCase):
422+ """Test case to check that proper arguments are passed to SSO backend."""
423+
424+ @inlineCallbacks
425+ def test_find_credentials(self):
426+ """The find_credentials method calls ubuntu_sso's method."""
427+ d = Deferred()
428+ self.proxy.find_credentials(reply_handler=lambda: d.callback(None),
429+ error_handler=d.errback)
430+ yield d
431+
432+ self.assertEqual(self.sso_server._app_name, APP_NAME)
433+ self.assertEqual(self.sso_server._args, {})
434+
435+ @inlineCallbacks
436+ def test_clear_credentials(self):
437+ """The clear_credentials method calls ubuntu_sso's method."""
438+ d = Deferred()
439+ self.proxy.clear_credentials(reply_handler=lambda: d.callback(None),
440+ error_handler=d.errback)
441+ yield d
442+
443+ self.assertEqual(self.sso_server._app_name, APP_NAME)
444+ self.assertEqual(self.sso_server._args, {})
445+
446+ @inlineCallbacks
447+ def test_store_credentials(self):
448+ """The store_credentials method calls ubuntu_sso's method."""
449+ d = Deferred()
450+ self.proxy.store_credentials(FAKED_CREDENTIALS,
451+ reply_handler=lambda: d.callback(None),
452+ error_handler=d.errback)
453+ yield d
454+
455+ self.assertEqual(self.sso_server._app_name, APP_NAME)
456+ self.assertEqual(self.sso_server._args, FAKED_CREDENTIALS)
457+
458+ @inlineCallbacks
459+ def test_register(self):
460+ """The register method calls ubuntu_sso's method."""
461+ d = Deferred()
462+ self.proxy.register(reply_handler=lambda: d.callback(None),
463+ error_handler=d.errback)
464+ yield d
465+
466+ self.assertEqual(self.sso_server._app_name, APP_NAME)
467+ params = {HELP_TEXT_KEY: DESCRIPTION, TC_URL_KEY: TC_URL,
468+ PING_URL_KEY: PING_URL, WINDOW_ID_KEY: '0'}
469+ self.assertEqual(self.sso_server._args, params)
470+
471+ @inlineCallbacks
472+ def test_login(self):
473+ """The login method calls ubuntu_sso's method."""
474+ d = Deferred()
475+ self.proxy.login(reply_handler=lambda: d.callback(None),
476+ error_handler=d.errback)
477+ yield d
478+
479+ self.assertEqual(self.sso_server._app_name, APP_NAME)
480+ params = {HELP_TEXT_KEY: DESCRIPTION, TC_URL_KEY: TC_URL,
481+ PING_URL_KEY: PING_URL, WINDOW_ID_KEY: '0'}
482+ self.assertEqual(self.sso_server._args, params)
483+
484+
485+class SameAppNoErrorTestCase(CredentialsManagementTestCase):
486+ """Test case when the app_name matches APP_NAME and there was no error."""
487+
488+ @inlineCallbacks
489+ def test_find_credentials(self):
490+ """The find_credentials method calls ubuntu_sso's method."""
491+ d = Deferred()
492+ yield self.add_credentials()
493+
494+ def verify(credentials):
495+ """Do the check."""
496+ self.assertEqual(credentials, FAKED_CREDENTIALS)
497+ self.deferred.callback(None)
498+
499+ self.connect_signals(callback=('CredentialsFound', verify))
500+
501+ self.proxy.find_credentials(reply_handler=lambda: d.callback(None),
502+ error_handler=d.errback)
503+ yield d
504+ yield self.do_test()
505+
506+ @inlineCallbacks
507+ def test_find_credentials_without_credentials(self):
508+ """The find_credentials method calls ubuntu_sso's method."""
509+ d = Deferred()
510+
511+ def verify():
512+ """Do the check."""
513+ self.deferred.callback(None)
514+
515+ self.connect_signals(callback=('CredentialsNotFound', verify))
516+
517+ self.proxy.find_credentials(reply_handler=lambda: d.callback(None),
518+ error_handler=d.errback)
519+ yield d
520+ yield self.do_test()
521+
522+ @inlineCallbacks
523+ def test_clear_credentials(self):
524+ """The clear_credentials method calls ubuntu_sso's method."""
525+ d = Deferred()
526+ yield self.add_credentials()
527+
528+ def verify():
529+ """Do the check."""
530+ self.deferred.callback(None)
531+
532+ self.connect_signals(callback=('CredentialsCleared', verify))
533+
534+ self.proxy.clear_credentials(reply_handler=lambda: d.callback(None),
535+ error_handler=d.errback)
536+ yield d
537+ yield self.do_test()
538+
539+ @inlineCallbacks
540+ def test_clear_credentials_without_credentials(self):
541+ """The clear_credentials method calls ubuntu_sso's method."""
542+ d = Deferred()
543+
544+ def verify():
545+ """Do the check."""
546+ self.deferred.callback(None)
547+
548+ self.connect_signals(callback=('CredentialsCleared', verify))
549+
550+ self.proxy.clear_credentials(reply_handler=lambda: d.callback(None),
551+ error_handler=d.errback)
552+ yield d
553+ yield self.do_test()
554+
555+ @inlineCallbacks
556+ def test_store_credentials(self):
557+ """The store_credentials method calls ubuntu_sso's method."""
558+ d = Deferred()
559+
560+ def verify():
561+ """Do the check."""
562+ self.deferred.callback(None)
563+
564+ self.connect_signals(callback=('CredentialsStored', verify))
565+
566+ self.proxy.store_credentials(FAKED_CREDENTIALS,
567+ reply_handler=lambda: d.callback(None),
568+ error_handler=d.errback)
569+ yield d
570+ yield self.do_test()
571+
572+ @inlineCallbacks
573+ def test_register_with_credentials(self):
574+ """The register method calls ubuntu_sso's method."""
575+ d = Deferred()
576+ yield self.add_credentials()
577+
578+ def verify(credentials):
579+ """Do the check."""
580+ self.assertEqual(credentials, FAKED_CREDENTIALS)
581+ self.deferred.callback(None)
582+
583+ self.connect_signals(callback=('CredentialsFound', verify))
584+
585+ self.proxy.register(reply_handler=lambda: d.callback(None),
586+ error_handler=d.errback)
587+ yield d
588+ yield self.do_test()
589+
590+ @inlineCallbacks
591+ def test_register_without_credentials(self):
592+ """The register method calls ubuntu_sso's method."""
593+ d = Deferred()
594+
595+ def verify(credentials):
596+ """Do the check."""
597+ self.assertEqual(credentials, FAKED_CREDENTIALS)
598+ self.deferred.callback(None)
599+
600+ self.connect_signals(callback=('CredentialsFound', verify))
601+
602+ self.proxy.register(reply_handler=lambda: d.callback(None),
603+ error_handler=d.errback)
604+ yield d
605+ yield self.do_test()
606+
607+ @inlineCallbacks
608+ def test_register_authorization_denied(self):
609+ """The register method calls ubuntu_sso's method."""
610+ d = Deferred()
611+ yield self.add_credentials(creds={})
612+
613+ def verify():
614+ """Do the check."""
615+ self.deferred.callback(None)
616+
617+ self.connect_signals(callback=('AuthorizationDenied', verify))
618+
619+ self.proxy.register(reply_handler=lambda: d.callback(None),
620+ error_handler=d.errback)
621+ yield d
622+ yield self.do_test()
623+
624+ @inlineCallbacks
625+ def test_login_with_credentials(self):
626+ """The login method calls ubuntu_sso's method."""
627+ d = Deferred()
628+ yield self.add_credentials()
629+
630+ def verify(credentials):
631+ """Do the check."""
632+ self.assertEqual(credentials, FAKED_CREDENTIALS)
633+ self.deferred.callback(None)
634+
635+ self.connect_signals(callback=('CredentialsFound', verify))
636+
637+ self.proxy.login(reply_handler=lambda: d.callback(None),
638+ error_handler=d.errback)
639+ yield d
640+ yield self.do_test()
641+
642+ @inlineCallbacks
643+ def test_login_without_credentials(self):
644+ """The login method calls ubuntu_sso's method."""
645+ d = Deferred()
646+
647+ def verify(credentials):
648+ """Do the check."""
649+ self.assertEqual(credentials, FAKED_CREDENTIALS)
650+ self.deferred.callback(None)
651+
652+ self.connect_signals(callback=('CredentialsFound', verify))
653+
654+ self.proxy.login(reply_handler=lambda: d.callback(None),
655+ error_handler=d.errback)
656+ yield d
657+ yield self.do_test()
658+
659+ @inlineCallbacks
660+ def test_login_authorization_denied(self):
661+ """The login method calls ubuntu_sso's method."""
662+ d = Deferred()
663+ yield self.add_credentials(creds={})
664+
665+ def verify():
666+ """Do the check."""
667+ self.deferred.callback(None)
668+
669+ self.connect_signals(callback=('AuthorizationDenied', verify))
670+
671+ self.proxy.login(reply_handler=lambda: d.callback(None),
672+ error_handler=d.errback)
673+ yield d
674+ yield self.do_test()
675+
676+
677+class SameAppWithErrorTestCase(SameAppNoErrorTestCase):
678+ """Test case when the app_name matches APP_NAME and there was an error."""
679+
680+ error_dict = {'error_type': 'Test'}
681+
682+ def connect_signals(self, callback=None):
683+ """CredentialsError is the success signals in this suite."""
684+
685+ def verify(error_dict):
686+ """Do the check."""
687+ self.assertEqual(error_dict, self.error_dict)
688+ self.deferred.callback(error_dict)
689+
690+ args = ('CredentialsError', verify)
691+ super(SameAppWithErrorTestCase, self).connect_signals(callback=args)
692+
693+
694+class OtherAppNoErrorTestCase(SameAppNoErrorTestCase):
695+ """Test case when the app_name is not APP_NAME and there was no error."""
696+
697+ app_name = APP_NAME * 2
698+
699+ def connect_signals(self, callback=None):
700+ """No signal should be received in this suite."""
701+ # ignore all success connection, self.deferred should always errback
702+ super(OtherAppNoErrorTestCase, self).connect_signals(callback=None)
703+
704+ @inlineCallbacks
705+ def do_test(self):
706+ """Perform the test itself."""
707+ if not self.deferred.called:
708+ msg = 'does not match %r, exiting.' % APP_NAME
709+ if self.memento.check_info(self.app_name, msg):
710+ self.deferred.callback(None)
711+ else:
712+ self.deferred.errback('Log should be present.')
713+
714+ yield self.deferred
715+
716+
717+class OtherAppWithErrorTestCase(OtherAppNoErrorTestCase):
718+ """Test case when the app_name is not APP_NAME and there was an error."""
719+
720+ app_name = APP_NAME * 2
721+ error_dict = {'error_type': 'Test'}
722
723=== modified file 'ubuntuone/clientdefs.py.in'
724--- ubuntuone/clientdefs.py.in 2010-12-07 18:01:03 +0000
725+++ ubuntuone/clientdefs.py.in 2011-01-05 15:54:10 +0000
726@@ -32,6 +32,7 @@
727 LIBEXECDIR = "@libexecdir@"
728 GETTEXT_PACKAGE = "@GETTEXT_PACKAGE@"
729
730+# these variables are Deprecated, use those defined in ubuntuone.credentials
731 APP_NAME = "@SSO_APP_NAME@"
732 TC_URL = "@SSO_TC_URL@"
733 PING_URL = "@SSO_PING_URL@"
734
735=== added directory 'ubuntuone/credentials'
736=== added file 'ubuntuone/credentials/__init__.py'
737--- ubuntuone/credentials/__init__.py 1970-01-01 00:00:00 +0000
738+++ ubuntuone/credentials/__init__.py 2011-01-05 15:54:10 +0000
739@@ -0,0 +1,205 @@
740+# -*- coding: utf-8 -*-
741+#
742+# Author: Natalia B. Bidart <natalia.bidart@canonical.com>
743+#
744+# Copyright 2010 Canonical Ltd.
745+#
746+# This program is free software: you can redistribute it and/or modify it
747+# under the terms of the GNU General Public License version 3, as published
748+# by the Free Software Foundation.
749+#
750+# This program is distributed in the hope that it will be useful, but
751+# WITHOUT ANY WARRANTY; without even the implied warranties of
752+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
753+# PURPOSE. See the GNU General Public License for more details.
754+#
755+# You should have received a copy of the GNU General Public License along
756+# with this program. If not, see <http://www.gnu.org/licenses/>.
757+
758+"""Ubuntu One credentials management dbus service."""
759+
760+import os
761+import sys
762+
763+import dbus.service
764+import gettext
765+import ubuntu_sso
766+
767+from ubuntu_sso.credentials import (HELP_TEXT_KEY, PING_URL_KEY, TC_URL_KEY,
768+ WINDOW_ID_KEY)
769+
770+from ubuntuone.logger import (basic_formatter, logging,
771+ CustomRotatingFileHandler, LOGFOLDER)
772+from ubuntuone.clientdefs import GETTEXT_PACKAGE
773+
774+
775+Q_ = lambda string: gettext.dgettext(GETTEXT_PACKAGE, string)
776+NO_OP = lambda *args, **kwargs: None
777+
778+LOG_LEVEL = logging.DEBUG
779+path = os.path.join(LOGFOLDER, 'credentials.log')
780+MAIN_HANDLER = CustomRotatingFileHandler(path)
781+MAIN_HANDLER.setFormatter(basic_formatter)
782+MAIN_HANDLER.setLevel(LOG_LEVEL)
783+
784+logger = logging.getLogger("ubuntuone.credentials")
785+logger.setLevel(LOG_LEVEL)
786+logger.addHandler(MAIN_HANDLER)
787+
788+if os.environ.get('DEBUG'):
789+ debug_handler = logging.StreamHandler(sys.stderr)
790+ debug_handler.setFormatter(basic_formatter)
791+ debug_handler.setLevel(LOG_LEVEL)
792+ logger.addHandler(debug_handler)
793+
794+# constants
795+DBUS_BUS_NAME = "com.ubuntuone.Credentials"
796+DBUS_CREDENTIALS_PATH = "/credentials"
797+DBUS_CREDENTIALS_IFACE = "com.ubuntuone.CredentialsManagement"
798+
799+APP_NAME = "Ubuntu One"
800+TC_URL = "https://one.ubuntu.com/terms/"
801+PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
802+DESCRIPTION = Q_('Ubuntu One requires an Ubuntu Single Sign On (SSO) account. '
803+ 'This process will allow you to create a new account, '
804+ 'if you do not yet have one.')
805+
806+
807+class CredentialsManagement(dbus.service.Object):
808+ """DBus object that manages Ubuntu One credentials."""
809+
810+ def __init__(self, *args, **kwargs):
811+ super(CredentialsManagement, self).__init__(*args, **kwargs)
812+ self.sso_match = None
813+ self.sso_proxy = self._get_sso_proxy()
814+
815+ def _signal_handler(self, *args, **kwargs):
816+ """Generic signal handler."""
817+ member = kwargs.get('member', None)
818+ app_name = args[0] if len(args) > 0 else None
819+ logger.debug('Handling DBus signal for member: %r, app_name: %r.',
820+ member, app_name)
821+
822+ if app_name != APP_NAME:
823+ logger.info('Received %r but app_name %r does not match %r, ' \
824+ 'exiting.', member, app_name, APP_NAME)
825+ return
826+
827+ sig = getattr(self, member)
828+
829+ if member in ('CredentialsFound', 'CredentialsError'):
830+ # this are the only signals that will forward the parameter
831+ logger.info('%r', member)
832+ arg = args[1]
833+ sig(arg)
834+ else:
835+ sig()
836+
837+ def _get_sso_proxy(self):
838+ """Get the SSO dbus proxy."""
839+ bus = dbus.SessionBus()
840+ # register signal handlers for each kind of error
841+ self.sso_match = bus.add_signal_receiver(self._signal_handler,
842+ member_keyword='member',
843+ dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE)
844+ try:
845+ obj = bus.get_object(ubuntu_sso.DBUS_BUS_NAME,
846+ ubuntu_sso.DBUS_CREDENTIALS_PATH,
847+ follow_name_owner_changes=True)
848+ proxy = dbus.Interface(obj, ubuntu_sso.DBUS_CREDENTIALS_IFACE)
849+ except:
850+ logger.exception('get_sso_proxy:')
851+ raise
852+
853+ return proxy
854+
855+ # Operator not preceded by a space (fails with dbus decorators)
856+ # pylint: disable=C0322
857+
858+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE)
859+ def AuthorizationDenied(self):
860+ """Signal thrown when the user denies the authorization."""
861+ logger.info('%s: emitting AuthorizationDenied.',
862+ self.__class__.__name__)
863+
864+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='a{ss}')
865+ def CredentialsFound(self, credentials):
866+ """Signal thrown when the credentials are found."""
867+ logger.info('%s: emitting CredentialsFound.',
868+ self.__class__.__name__)
869+
870+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE)
871+ def CredentialsNotFound(self):
872+ """Signal thrown when the credentials are not found."""
873+ logger.info('%s: emitting CredentialsNotFound.',
874+ self.__class__.__name__)
875+
876+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE)
877+ def CredentialsCleared(self):
878+ """Signal thrown when the credentials were cleared."""
879+ logger.info('%s: emitting CredentialsCleared.',
880+ self.__class__.__name__)
881+
882+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE)
883+ def CredentialsStored(self):
884+ """Signal thrown when the credentials were cleared."""
885+ logger.info('%s: emitting CredentialsStored.',
886+ self.__class__.__name__)
887+
888+ @dbus.service.signal(DBUS_CREDENTIALS_IFACE, signature='a{ss}')
889+ def CredentialsError(self, error_dict):
890+ """Signal thrown when there is a problem getting the credentials."""
891+ logger.error('%s: emitting CredentialsError with error_dict %r.',
892+ self.__class__.__name__, error_dict)
893+
894+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
895+ async_callbacks=("reply_handler", "error_handler"))
896+ def find_credentials(self, reply_handler=NO_OP, error_handler=NO_OP):
897+ """Ask the Ubuntu One credentials."""
898+ self.sso_proxy.find_credentials(APP_NAME, {},
899+ reply_handler=reply_handler, error_handler=error_handler)
900+
901+
902+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
903+ async_callbacks=("reply_handler", "error_handler"))
904+ def clear_credentials(self, reply_handler=NO_OP, error_handler=NO_OP):
905+ """Clear the Ubuntu One credentials."""
906+ self.sso_proxy.clear_credentials(APP_NAME, {},
907+ reply_handler=reply_handler, error_handler=error_handler)
908+
909+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
910+ in_signature='a{ss}',
911+ async_callbacks=("reply_handler", "error_handler"))
912+ def store_credentials(self, credentials,
913+ reply_handler=NO_OP, error_handler=NO_OP):
914+ """Store the token for Ubuntu One application."""
915+ self.sso_proxy.store_credentials(APP_NAME, credentials,
916+ reply_handler=reply_handler, error_handler=error_handler)
917+
918+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
919+ async_callbacks=("reply_handler", "error_handler"))
920+ def register(self, reply_handler=NO_OP, error_handler=NO_OP):
921+ """Get credentials if found else prompt to register to Ubuntu One."""
922+ params = {HELP_TEXT_KEY: DESCRIPTION, TC_URL_KEY: TC_URL,
923+ PING_URL_KEY: PING_URL, WINDOW_ID_KEY: '0'}
924+ self.sso_proxy.register(APP_NAME, params,
925+ reply_handler=reply_handler, error_handler=error_handler)
926+
927+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
928+ async_callbacks=("reply_handler", "error_handler"))
929+ def login(self, reply_handler=NO_OP, error_handler=NO_OP):
930+ """Get credentials if found else prompt to login to Ubuntu One."""
931+ self.register(reply_handler, error_handler)
932+
933+ @dbus.service.method(dbus_interface=DBUS_CREDENTIALS_IFACE,
934+ async_callbacks=("reply_handler", "error_handler"))
935+ def shutdown(self, reply_handler=NO_OP, error_handler=NO_OP):
936+ """Disconnect signals from SSO backend."""
937+ bus = dbus.SessionBus()
938+ try:
939+ bus.remove_signal_receiver(self.sso_match, member_keyword='member',
940+ dbus_interface=ubuntu_sso.DBUS_CREDENTIALS_IFACE)
941+ except Exception, e:
942+ error_handler(e)
943+ else:
944+ reply_handler()

Subscribers

People subscribed via source and target branches