Merge lp:~verterok/ubuntuone-client/sd-do-oauth into lp:ubuntuone-client

Proposed by Guillermo Gonzalez
Status: Merged
Approved by: dobey
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~verterok/ubuntuone-client/sd-do-oauth
Merge into: lp:ubuntuone-client
Diff against target: 384 lines (+243/-12)
4 files modified
contrib/testing/testcase.py (+64/-5)
tests/syncdaemon/test_dbus.py (+113/-1)
tests/syncdaemon/test_fsm.py (+2/-1)
ubuntuone/syncdaemon/dbus_interface.py (+64/-5)
To merge this branch: bzr merge lp:~verterok/ubuntuone-client/sd-do-oauth
Reviewer Review Type Date Requested Status
dobey (community) Approve
John O'Brien (community) Approve
Review via email: mp+15775@code.launchpad.net

Commit message

Use oauthdesktop in syncdaemon to request a token if we haven't already got one.

To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

This branch adds support for oauthdesktop to syncdaemon.
e.g: If there is no token in the keyring, syncdaemon will call oauthdesktop to get the token, and listen for the oauthdesktop DBus signals.

Revision history for this message
John O'Brien (jdobrien) wrote :

works great.

review: Approve
Revision history for this message
dobey (dobey) wrote :

I get this when doing make check. I'm not sure why though, as I'm running it as me.

rm: cannot remove directory `_trial_temp/tmp/tests.syncdaemon.test_tools/TestToolsBasic/test_accept_share/tmpdir/shares_dir/share': Permission denied
rm: cannot remove directory `_trial_temp/tmp/tests.syncdaemon.test_dbus/DBusInterfaceTests/test_accept_share/tmpdir/shares_dir/share': Permission denied
make: *** [check] Error 1

review: Needs Fixing
Revision history for this message
dobey (dobey) wrote :

I get this when doing make check. It looks like for some reason the "share" directory here isn't writable by me.

rm: cannot remove directory `_trial_temp/tmp/tests.syncdaemon.test_tools/TestToolsBasic/test_accept_share/tmpdir/shares_dir/share': Permission denied
rm: cannot remove directory `_trial_temp/tmp/tests.syncdaemon.test_dbus/DBusInterfaceTests/test_accept_share/tmpdir/shares_dir/share': Permission denied
make: *** [check] Error 1

review: Needs Fixing
285. By Guillermo Gonzalez

fix tests tearDown

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

Fixed and pushed.

Thanks

Revision history for this message
dobey (dobey) wrote :

Now I'm intermittently getting the following failure:

[FAIL]: tests.syncdaemon.test_fsm.FileHandlingTests.test_warning_on_log_file_when_failing_delete

Traceback (most recent call last):
  File "/usr/lib/python2.6/unittest.py", line 279, in run
    testMethod()
  File "/home/dobey/Projects/canonical/ubuntuone-client/sd-do-oauth/tests/syncdaemon/test_fsm.py", line 1909, in test_warning_on_log_file_when_failing_delete
    self.assertTrue(log_present)
exceptions.AssertionError:
-------------------------------------------------------------------------------
Ran 4499 tests in 81.800s

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

Hi,
That test is unrelated to my changes.
I'll file a bug about it and skip the test.

On Dec 8, 2009 4:01 PM, "Rodney Dawes" <email address hidden> wrote:

Now I'm intermittently getting the following failure:

[FAIL]:
tests.syncdaemon.test_fsm.FileHandlingTests.test_warning_on_log_file_when_failing_delete

Traceback (most recent call last):
 File "/usr/lib/python2.6/unittest.py", line 279, in run
   testMethod()
 File
"/home/dobey/Projects/canonical/ubuntuone-client/sd-do-oauth/tests/syncdaemon/test_fsm.py",
line 1909, in test_warning_on_log_file_when_failing_delete
   self.assertTrue(log_present)
exceptions.AssertionError:
-------------------------------------------------------------------------------
Ran 4499 tests in 81.800s

--
https://code.edge.launchpad.net/~verterok/ubuntuone-client/sd-do-oauth/+merge/15775

You are the owner of lp:~verterok/ubuntuone-client/sd-do-oauth.

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

Hi, Bug #494469 filed and assigned.

Thanks

286. By Guillermo Gonzalez

skip test_warning_on_log_file_when_failing_delete, Bug #494469

Revision history for this message
dobey (dobey) wrote :

OK. Looks like the test isn't failing now, and several runs all passed, so I'll go ahead and Approve. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'contrib/testing/testcase.py'
2--- contrib/testing/testcase.py 2009-10-14 18:38:59 +0000
3+++ contrib/testing/testcase.py 2009-12-09 15:01:13 +0000
4@@ -23,7 +23,7 @@
5 import os
6 import shutil
7
8-
9+from ubuntuone.oauthdesktop.main import Login, LoginProcessor
10 from ubuntuone.syncdaemon import (
11 config,
12 action_queue,
13@@ -38,10 +38,12 @@
14 DBusExposedObject,
15 NM_STATE_CONNECTED,
16 NM_STATE_DISCONNECTED,
17+ DBUS_IFACE_AUTH_NAME,
18 )
19 from twisted.internet import defer
20 from twisted.trial.unittest import TestCase as TwistedTestCase
21 from zope.interface import implements
22+from oauth import oauth
23
24 class FakeOAuthClient(object):
25 """ Fake OAuthClient"""
26@@ -49,7 +51,7 @@
27 def __init__(self, realm):
28 """ create the instance. """
29 self.realm = realm
30- self.consumer = 'ubuntuone'
31+ self.consumer = oauth.OAuthConsumer('ubuntuone', 'hammertime')
32
33 def get_access_token(self):
34 """ returns a Token"""
35@@ -253,7 +255,7 @@
36
37 def tearDown(self):
38 """ Cleanup the test. """
39- d = self.cleanup_signal_receivers()
40+ d = self.cleanup_signal_receivers(self.signal_receivers)
41 d.addBoth(self._tearDown)
42 d.addBoth(lambda _: BaseTwistedTestCase.tearDown(self))
43 return d
44@@ -276,10 +278,10 @@
45 """ default error handler for DBus calls. """
46 self.fail(error)
47
48- def cleanup_signal_receivers(self):
49+ def cleanup_signal_receivers(self, signal_receivers):
50 """ cleanup self.signal_receivers and returns a deferred """
51 deferreds = []
52- for match in self.signal_receivers:
53+ for match in signal_receivers:
54 d = defer.Deferred()
55 def callback(*args):
56 """ callback that accepts *args. """
57@@ -305,6 +307,7 @@
58 """ Creates the instance """
59 self.eq = self.event_queue = eq
60 self.client = action_queue.ActionQueueProtocol()
61+ self.client.disconnect = lambda: None
62 self.uploading = {}
63 self.downloading = {}
64 # pylint: disable-msg=C0103
65@@ -482,3 +485,59 @@
66 """
67 Do nothing
68 """
69+
70+# OAuth stubs
71+class FakeLoginProcessor(LoginProcessor):
72+ """Stub login processor."""
73+
74+ def __init__(self, dbus_object):
75+ """Initialize the login processor."""
76+ LoginProcessor.__init__(self, dbus_object, use_libnotify=False)
77+ self.next_login_cb = None
78+
79+ def login(self, realm, consumer_key, error_handler=None, reply_handler=None, do_login=True):
80+ """Stub, call self.next_login_cb or send NewCredentials if
81+ self.next_login_cb isn't defined.
82+ """
83+ self.realm = str(realm)
84+ self.consumer_key = str(consumer_key)
85+ if self.next_login_cb:
86+ cb = self.next_login_cb[0]
87+ args = self.next_login_cb[1]
88+ self.next_login_cb = None
89+ return cb(*args)
90+ else:
91+ self.dbus_object.NewCredentials(realm, consumer_key)
92+
93+ def clear_token(self, realm, consumer_key):
94+ """Stub, do nothing"""
95+ pass
96+
97+ def next_login_with(self, callback, args=tuple()):
98+ """shortcircuit the next call to login and call the specified callback.
99+ callback is usually one of: self.got_token, self.got_no_token,
100+ self.got_denial or self.got_error.
101+ """
102+ self.next_login_cb = (callback, args)
103+
104+
105+class FakeLogin(Login):
106+ """Stub Object which listens for D-Bus OAuth requests"""
107+
108+ def __init__(self, bus):
109+ """Initiate the object."""
110+ self.bus = bus
111+ self.busName = dbus.service.BusName(DBUS_IFACE_AUTH_NAME, bus=self.bus)
112+ # bypass the parent class __init__ as it has the path hardcoded
113+ # and we can't use '/' as the path, as we are already using it
114+ # for syncdaemon. pylint: disable-msg=W0233,W0231
115+ dbus.service.Object.__init__(self, object_path="/oauthdesktop",
116+ bus_name=self.busName)
117+ self.processor = FakeLoginProcessor(self)
118+ self.currently_authing = False
119+
120+ def shutdown(self):
121+ """Shutdown and remove any trace from the bus"""
122+ self.busName.get_bus().release_name(self.busName.get_name())
123+ self.remove_from_connection()
124+
125
126=== modified file 'tests/syncdaemon/test_dbus.py'
127--- tests/syncdaemon/test_dbus.py 2009-11-20 22:00:25 +0000
128+++ tests/syncdaemon/test_dbus.py 2009-12-09 15:01:13 +0000
129@@ -24,6 +24,7 @@
130 from ubuntuone.storageprotocol.sharersp import (
131 NotifyShareHolder,
132 )
133+from ubuntuone.syncdaemon import dbus_interface
134 from ubuntuone.syncdaemon.dbus_interface import (
135 DBUS_IFACE_STATUS_NAME,
136 DBUS_IFACE_EVENTS_NAME,
137@@ -36,9 +37,10 @@
138 from ubuntuone.syncdaemon.volume_manager import Share
139 from ubuntuone.syncdaemon.tools import DBusClient
140 from ubuntuone.syncdaemon import event_queue
141-from ubuntuone.syncdaemon import states
142+from ubuntuone.syncdaemon import states, main
143 from contrib.testing.testcase import (
144 DBusTwistedTestCase,
145+ FakeLogin,
146 )
147 from twisted.internet import defer
148 from ubuntuone.syncdaemon.marker import MDMarker
149@@ -1297,6 +1299,116 @@
150 return d
151
152
153+class TestDBusOAuth(DBusTwistedTestCase):
154+ """Tests the interaction between dbus_interface and oauthdesktop"""
155+
156+ def setUp(self):
157+ DBusTwistedTestCase.setUp(self)
158+ self.oauth = FakeLogin(self.bus)
159+ self._old_path = dbus_interface.DBUS_PATH_AUTH
160+ dbus_interface.DBUS_PATH_AUTH = '/oauthdesktop'
161+
162+ def tearDown(self):
163+ # collect all signal receivers registered during the test
164+ signal_receivers = set()
165+ with self.bus._signals_lock:
166+ for group in self.bus._signal_recipients_by_object_path.values():
167+ for matches in group.values():
168+ for match in matches.values():
169+ signal_receivers.update(match)
170+ d = self.cleanup_signal_receivers(signal_receivers)
171+ def shutdown(r):
172+ self.oauth.shutdown()
173+ dbus_interface.DBUS_PATH_AUTH = self._old_path
174+ d.addCallback(shutdown)
175+ d.addCallback(lambda _: DBusTwistedTestCase.tearDown(self))
176+ return d
177+
178+ def test_new_credentials_signal(self):
179+ """NewCredentials signal test"""
180+ self.oauth.processor.next_login_with(self.oauth.processor.got_token,
181+ ('my_token',))
182+ d = self.dbus_iface._request_token()
183+ def check(info):
184+ realm, key = info
185+ expected = (u'http://test.ubuntuone.com', u'ubuntuone')
186+ self.assertEquals((realm, key), expected)
187+ d.addCallbacks(check, self.fail)
188+ return d
189+
190+ def test_no_credentials_signal(self):
191+ """NoCredentials signal test"""
192+ self.oauth.processor.next_login_with(self.oauth.processor.got_no_token)
193+ d = self.dbus_iface._request_token()
194+ def check(f):
195+ self.assertEquals(f.getErrorMessage(), 'NoCredentials')
196+ d.addCallbacks(self.fail, check)
197+ return d
198+
199+ def test_auth_denied_signal(self):
200+ """AuthorizationDenied signal test"""
201+ self.oauth.processor.next_login_with(self.oauth.processor.got_denial)
202+ d = self.dbus_iface._request_token()
203+ def check(f):
204+ self.assertEquals(f.getErrorMessage(), 'AuthorizationDenied')
205+ d.addCallbacks(self.fail, check)
206+ return d
207+
208+ def test_oauth_error_signal(self):
209+ """OAuthError signal test"""
210+ self.oauth.processor.next_login_with(self.oauth.processor.got_error,
211+ ('error!',))
212+ d = self.dbus_iface._request_token()
213+ def check(f):
214+ self.assertEquals(f.getErrorMessage(), 'OAuthError: error!')
215+ d.addCallbacks(self.fail, check)
216+ return d
217+
218+ def test_connect_without_token(self):
219+ """Test connecting without having a token"""
220+ # replace the get_access_token method in Main
221+ def get_access_token():
222+ raise main.NoAccessToken('no token')
223+ self.main.get_access_token = get_access_token
224+ self.dbus_iface.disconnect()
225+ self.oauth.processor.next_login_with(self.oauth.processor.got_token,
226+ ('the token',))
227+ d = self.dbus_iface.connect()
228+ def check(result):
229+ self.assertEquals(result, None)
230+ d.addCallbacks(check, self.fail)
231+ return d
232+
233+ def test_connect_with_token(self):
234+ """Test connecting with a token"""
235+ self.dbus_iface.disconnect()
236+ self.oauth.processor.next_login_with(self.oauth.processor.got_token,
237+ ('the token',))
238+ d = self.dbus_iface.connect()
239+ # check that the deferred was fired
240+ self.assertEquals(d.called, True)
241+ self.assertEquals(d.result, None)
242+ d.addCallbacks(lambda r: self.assertEquals(r, None), self.fail)
243+ return d
244+
245+ def test_error_on_login(self):
246+ """Test connecting without having a token and getting an error in the
247+ call to oauthdesktop login().
248+ """
249+ # replace the get_access_token method in Main
250+ def get_access_token():
251+ raise main.NoAccessToken('no token')
252+ self.main.get_access_token = get_access_token
253+ self.dbus_iface.disconnect()
254+ def broken_login(*args):
255+ raise ValueError('oops, login is broken!')
256+ self.oauth.processor.next_login_with(broken_login)
257+ d = self.dbus_iface.connect()
258+ def check(result):
259+ self.assertIn('ValueError: oops, login is broken!', result.getErrorMessage())
260+ d.addCallbacks(self.fail, check)
261+ return d
262+
263
264 def test_suite():
265 # pylint: disable-msg=C0111
266
267=== modified file 'tests/syncdaemon/test_fsm.py'
268--- tests/syncdaemon/test_fsm.py 2009-12-01 20:46:11 +0000
269+++ tests/syncdaemon/test_fsm.py 2009-12-09 15:01:13 +0000
270@@ -1888,7 +1888,8 @@
271 log_present = 'OSError [Errno 39] Directory not empty' in log_content
272 self.assertFalse(log_present)
273
274- def test_warning_on_log_file_when_failing_delete(self):
275+ # FIXME: Bug #494469
276+ def SKIP_test_warning_on_log_file_when_failing_delete(self):
277 """Test that sucessfully deleted dir does not log OSError."""
278
279 log = open(LOGFILENAME, 'r')
280
281=== modified file 'ubuntuone/syncdaemon/dbus_interface.py'
282--- ubuntuone/syncdaemon/dbus_interface.py 2009-11-18 16:13:01 +0000
283+++ ubuntuone/syncdaemon/dbus_interface.py 2009-12-09 15:01:13 +0000
284@@ -16,14 +16,20 @@
285 # You should have received a copy of the GNU General Public License along
286 # with this program. If not, see <http://www.gnu.org/licenses/>.
287 """ DBUS interface module """
288+
289+import dbus.service
290+import logging
291+
292+from dbus import DBusException
293 from itertools import groupby, chain
294
295-import dbus.service
296+from twisted.internet import defer
297+from twisted.python.failure import Failure
298
299 from ubuntuone.syncdaemon.event_queue import EVENTS
300 from ubuntuone.syncdaemon.interfaces import IMarker
301 from ubuntuone.syncdaemon.config import get_user_config
302-import logging
303+
304
305 # Disable the "Invalid Name" check here, as we have lots of DBus style names
306 # pylint: disable-msg=C0103
307@@ -46,6 +52,9 @@
308 NM_STATE_EVENTS = {NM_STATE_CONNECTED: 'SYS_NET_CONNECTED',
309 NM_STATE_DISCONNECTED: 'SYS_NET_DISCONNECTED'}
310
311+# OAuthDesktop constants
312+DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
313+DBUS_PATH_AUTH = "/"
314
315 logger = logging.getLogger("ubuntuone.SyncDaemon.DBus")
316
317@@ -1065,14 +1074,64 @@
318 if event is not None:
319 self.event_queue.push(event)
320
321- def connect(self):
322- """ Push the SYS_CONNECT event with the tokens in the keyring. """
323+ @defer.inlineCallbacks
324+ def connect(self, do_login=True):
325+ """Push the SYS_CONNECT event with the token in the keyring or
326+ request the token via oauthdesktop and push the acquired token."""
327 from ubuntuone.syncdaemon.main import NoAccessToken
328 try:
329 access_token = self.main.get_access_token()
330 self.event_queue.push('SYS_CONNECT', access_token)
331 except NoAccessToken, e:
332- logger.exception("Can't get the auth token")
333+ if do_login:
334+ yield self._request_token()
335+ self.connect(do_login=False)
336+ else:
337+ logger.exception("Can't get the auth token")
338+
339+ def _request_token(self):
340+ """Request to OAuthDesktop to fetch the token"""
341+ from ubuntuone.syncdaemon.main import NoAccessToken
342+ d = defer.Deferred()
343+ def error_handler(error):
344+ """default dbus error handler"""
345+ if not d.called:
346+ d.errback(Failure(error))
347+
348+ def signal_handler(*args, **kwargs):
349+ """Signal handler"""
350+ member = kwargs.get('member', None)
351+ if member in ('NoCredentials', 'AuthorizationDenied',
352+ 'OAuthError'):
353+ if not args:
354+ d.errback(Failure(NoAccessToken(member)))
355+ else:
356+ d.errback(Failure(NoAccessToken("%s: %s" %
357+ (member, args[0]))))
358+ elif member == 'NewCredentials' and not d.called:
359+ d.callback(args)
360+ # register signal handlers for each kind of error
361+ match = self.bus.add_signal_receiver(signal_handler,
362+ member_keyword='member',
363+ dbus_interface=DBUS_IFACE_AUTH_NAME)
364+ # call oauthdesktop
365+ try:
366+ client = self.bus.get_object(DBUS_IFACE_AUTH_NAME, DBUS_PATH_AUTH,
367+ follow_name_owner_changes=True)
368+ iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
369+ iface.login(self.main.realm, self.main.oauth_client.consumer.key,
370+ # ignore the reply, we get the result via signals
371+ reply_handler=lambda: None,
372+ error_handler=error_handler)
373+ except DBusException, e:
374+ d.errback(Failure(e))
375+ def remove_signal_receiver(r):
376+ # cleanup the signal receivers
377+ self.bus.remove_signal_receiver(match,
378+ dbus_interface=DBUS_IFACE_AUTH_NAME)
379+ return r
380+ d.addBoth(remove_signal_receiver)
381+ return d
382
383 def disconnect(self):
384 """ Push the SYS_DISCONNECT event. """

Subscribers

People subscribed via source and target branches