Merge lp:~nataliabidart/ubuntuone-client/auth into lp:ubuntuone-client
- auth
- Merge into trunk
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 | ||||
Related bugs: |
|
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
and play with the methods using d-feet, accessing the com.ubuntuone.
- 780. By Natalia Bidart
-
Adding installation specific bits.
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.
Guillermo Gonzalez (verterok) wrote : | # |
looks good.
- 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.
dobey (dobey) wrote : | # |
I still strongly believe that the logging pieces and the CredentialsMana
Preview Diff
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() |
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.