Merge lp:~nataliabidart/ubuntuone-client/split-oauth into lp:ubuntuone-client

Proposed by Natalia Bidart
Status: Merged
Approved by: dobey
Approved revision: 542
Merged at revision: 543
Proposed branch: lp:~nataliabidart/ubuntuone-client/split-oauth
Merge into: lp:ubuntuone-client
Diff against target: 2363 lines (+10/-2166)
22 files modified
Makefile.am (+0/-1)
bin/ubuntuone-login (+0/-238)
bin/ubuntuone-preferences (+4/-5)
contrib/testing/testcase.py (+1/-1)
data/Makefile.am (+2/-7)
data/com.ubuntuone.Authentication.service.in (+0/-3)
data/oauth_registration.d/ubuntuone (+0/-23)
data/oauth_urls (+0/-5)
tests/oauthdesktop/__init__.py (+0/-14)
tests/oauthdesktop/test_auth.py (+0/-263)
tests/oauthdesktop/test_config.py (+0/-90)
tests/oauthdesktop/test_key_acls.py (+0/-181)
tests/oauthdesktop/test_main.py (+0/-93)
tests/test_login.py (+0/-187)
tests/test_preferences.py (+2/-0)
ubuntuone/oauthdesktop/__init__.py (+0/-16)
ubuntuone/oauthdesktop/auth.py (+0/-480)
ubuntuone/oauthdesktop/config.py (+0/-47)
ubuntuone/oauthdesktop/key_acls.py (+0/-162)
ubuntuone/oauthdesktop/logger.py (+0/-49)
ubuntuone/oauthdesktop/main.py (+0/-296)
ubuntuone/syncdaemon/dbus_interface.py (+1/-5)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-client/split-oauth
Reviewer Review Type Date Requested Status
dobey (community) Approve
Rodrigo Moya (community) Approve
Ubuntu One hackers Pending
Review via email: mp+26946@code.launchpad.net

Commit message

Split out the code for oauthdesktop.

Description of the change

Split out the code for oauthdesktop.

Until the package for ubuntu-sso-client is built, you'll need to manually add symlinks to run the tests as per:

nessita@dali:~/canonical/ubuntuone-client/split-oauth$ ls -l bin/
lrwxrwxrwx 1 nessita nessita 49 2010-06-09 15:54 ubuntu-login -> ../../../ubuntu-sso-client/trunk/bin/ubuntu-login

nessita@dali:~/canonical/ubuntuone-client/split-oauth$ ls -l .
lrwxrwxrwx 1 nessita nessita 36 2010-06-09 15:54 ubuntu -> ../../ubuntu-sso-client/trunk/ubuntu

To post a comment you must log in.
Revision history for this message
Rodrigo Moya (rodrigo-moya) wrote :

Let's first finish the renaming of the Python packages and DBus service in ubuntu-sso-client, and then let's change this branch to reflect that renaming, so that we don't need to do any further changes (at least renames) in u1-client

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

> Let's first finish the renaming of the Python packages and DBus service in
> ubuntu-sso-client, and then let's change this branch to reflect that renaming,
> so that we don't need to do any further changes (at least renames) in
> u1-client

Renamed was finished.

Revision history for this message
dobey (dobey) wrote :

The tests/test_login.py needs to be moved to the ubuntu-sso-client tree, rather than staying here. Ideally the oauthdesktop code should be fixed to not use twisted for the temporary local http server any more. And in fact, it won't need to have a local server after it is fixed to not use the browser for doing auth. Moving this test will also obviate the need for having the symlink to ubuntu-login.

We're also going to need to have the symlink creation either be automated (or unneccessary).

Given the hour though, we can discuss it more tomorrow.

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

I think we can solve the test-login thing in another branch, so approve from me

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

Can we simplify ubuntu-sso-client to just provide the "ubuntusso" namespace, rather than the empty ubuntu namespace (which may present future conflicts) with the sso sub package?

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

> Can we simplify ubuntu-sso-client to just provide the "ubuntusso" namespace,
> rather than the empty ubuntu namespace (which may present future conflicts)
> with the sso sub package?

Name simplified to ubuntu_sso.

534. By Natalia Bidart

Merged trunk in.

535. By Natalia Bidart

Applying seconds rename for Ubuntu SSO.

536. By Natalia Bidart

Making tests no depend on sso-client tests.

537. By Natalia Bidart

Restoting FakeLogin and FakeProcessor.

538. By Natalia Bidart

Merged trunk in.

539. By Natalia Bidart

Skipping test_login.

540. By Natalia Bidart

Poiting to the righ binary.

541. By Natalia Bidart

Merged trunk in.

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

Package ubuntu-sso-client is being sponsored and soon to be on main. In the mean time, it can be downloaded from https://launchpad.net/~nataliabidart/+archive/ppa

The split is ready to go into trunk.

Revision history for this message
dobey (dobey) wrote :

Please just remove the test_login.py in this branch. I have a branch for ubuntu-sso-client nearly ready to propose, which moves it there.

review: Needs Fixing
542. By Natalia Bidart

Removing test_login which will be moved to ubuntu-sso-client project.

Revision history for this message
dobey (dobey) wrote :

OK. Aside from potential smaller issues that might crop up, and some possible small changes we might need to make to use ubuntu-sso-client uninstalled, this seems ok to me now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile.am'
--- Makefile.am 2010-06-11 15:33:16 +0000
+++ Makefile.am 2010-06-16 20:35:34 +0000
@@ -25,7 +25,6 @@
25 bin/u1sync25 bin/u1sync
2626
27libexec_SCRIPTS = \27libexec_SCRIPTS = \
28 bin/ubuntuone-login \
29 bin/ubuntuone-syncdaemon28 bin/ubuntuone-syncdaemon
3029
31manfilesdir = $(mandir)/man130manfilesdir = $(mandir)/man1
3231
=== removed file 'bin/ubuntuone-login'
--- bin/ubuntuone-login 2010-04-14 23:31:21 +0000
+++ bin/ubuntuone-login 1970-01-01 00:00:00 +0000
@@ -1,238 +0,0 @@
1#!/usr/bin/python
2
3# ubuntuone-login - Client side log-in utility for Ubuntu One
4#
5# Author: Rodney Dawes <rodney.dawes@canonical.com>
6#
7# Copyright 2009 Canonical Ltd.
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20
21import pygtk
22pygtk.require('2.0')
23import gtk
24import pango
25import sys
26import gettext
27from ubuntuone import clientdefs
28
29import dbus.service
30
31from dbus.mainloop.glib import DBusGMainLoop
32from ubuntuone.oauthdesktop.main import Login
33
34from ubuntuone.oauthdesktop.logger import setupLogging
35logger = setupLogging("ubuntuone-login")
36
37DBusGMainLoop(set_as_default=True)
38
39_ = gettext.gettext
40ngettext = gettext.ngettext
41
42DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
43
44OAUTH_REALM = "https://ubuntuone.com"
45OAUTH_CONSUMER = "ubuntuone"
46
47NOTIFY_ICON_SIZE = 48
48
49# Why thank you GTK+ for enforcing style-set and breaking API
50RCSTYLE = """
51style 'dialogs' {
52 GtkDialog::action-area-border = 12
53 GtkDialog::button-spacing = 6
54 GtkDialog::content-area-border = 0
55}
56widget_class '*Dialog*' style 'dialogs'
57"""
58
59
60class LoginMain(object):
61 """Main login manager process class."""
62
63 def __init__(self, *args, **kw):
64 """Initializes the child threads and dbus monitor."""
65 gtk.rc_parse_string(RCSTYLE)
66
67 logger.info(_("Starting Ubuntu One login manager version %s") %
68 clientdefs.VERSION)
69
70 self.__bus = dbus.SessionBus()
71
72 def _connect_dbus_signals(self):
73 """Set up some signal handlers for DBus signals."""
74 self.__bus.add_signal_receiver(
75 handler_function=self.new_credentials,
76 signal_name="NewCredentials",
77 dbus_interface=DBUS_IFACE_AUTH_NAME)
78 self.__bus.add_signal_receiver(
79 handler_function=self.auth_denied,
80 signal_name="AuthorizationDenied",
81 dbus_interface=DBUS_IFACE_AUTH_NAME)
82 self.__bus.add_signal_receiver(
83 handler_function=self.got_oauth_error,
84 signal_name="OAuthError",
85 dbus_interface=DBUS_IFACE_AUTH_NAME)
86
87
88 def new_credentials(self, realm=None, consumer_key=None, sender=None):
89 """Signal callback for when we get new credentials."""
90 self.set_up_desktopcouch_pairing(consumer_key)
91
92 def got_port(*args, **kwargs):
93 # Discard the value. We don't really care about the port, here.
94 pass
95
96 def got_error(*args, **kwargs):
97 logger.warn("On trying to start desktopcouch-service via DBus, "
98 "we got an error. %s %s" % (args, kwargs,))
99
100 # We have auth, so start desktopcouch service by asking for its port.
101 dc_serv_proxy = self.__bus.get_object("org.desktopcouch.CouchDB", "/")
102 dc_serv_proxy.getPort(reply_handler=got_port, error_handler=got_error)
103
104 def auth_denied(self):
105 """Signal callback for when auth was denied by user."""
106 logger.info(_("Access to Ubuntu One was denied."))
107
108 from twisted.internet import reactor
109 reactor.stop()
110
111 def got_oauth_error(self, message=None):
112 """Signal callback for when an OAuth error occured."""
113 def dialog_response(dialog, response):
114 """Handle the dialog closing."""
115 dialog.destroy()
116
117 if message:
118 logger.error(message)
119 dialog = gtk.Dialog(title=_("Ubuntu One: Error"),
120 flags=gtk.DIALOG_NO_SEPARATOR,
121 buttons=(gtk.STOCK_CLOSE,
122 gtk.RESPONSE_CLOSE))
123 dialog.set_default_response(gtk.RESPONSE_CLOSE)
124 dialog.set_icon_name("ubuntuone-client")
125
126 area = dialog.get_content_area()
127
128 hbox = gtk.HBox(spacing=12)
129 hbox.set_border_width(12)
130 area.pack_start(hbox)
131 hbox.show()
132
133 image = gtk.Image()
134 image.set_from_icon_name("dialog-error", gtk.ICON_SIZE_DIALOG)
135 image.set_alignment(0.5, 0.0)
136 image.show()
137 hbox.pack_start(image, False, False)
138
139 vbox = gtk.VBox(spacing=12)
140 vbox.show()
141 hbox.pack_start(vbox)
142
143 label = gtk.Label("<b>%s</b>" % _("Authorization Error"))
144 label.set_use_markup(True)
145 label.set_alignment(0.0, 0.5)
146 label.show()
147 vbox.pack_start(label, False, False)
148
149 label = gtk.Label(message)
150 label.set_line_wrap(True)
151 label.set_max_width_chars(64)
152 label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
153 label.set_alignment(0.0, 0.0)
154 label.show()
155 vbox.pack_start(label, True, True)
156
157 dialog.connect('close', dialog_response, gtk.RESPONSE_CLOSE)
158 dialog.connect('response', dialog_response)
159
160 dialog.show()
161 else:
162 logger.error(_("Got an OAuth error with no message."))
163
164 def set_up_desktopcouch_pairing(self, consumer_key):
165 """Add a pairing record between desktopcouch and Ubuntu One"""
166 try:
167 from desktopcouch.pair.couchdb_pairing.couchdb_io import \
168 put_static_paired_service
169 from desktopcouch.records.server import CouchDatabase
170 except ImportError:
171 # desktopcouch is not installed
172 logger.debug(_("Not adding desktopcouch pairing since"
173 " desktopcouch is not installed"))
174 return
175 # Check whether there is already a record of the Ubuntu One service
176 db = CouchDatabase("management", create=True)
177 if not db.view_exists("ubuntu_one_pair_record","ubuntu_one_pair_record"):
178 map_js = """function(doc) {
179 if (doc.service_name == "ubuntuone") {
180 if (doc.application_annotations &&
181 doc.application_annotations["Ubuntu One"] &&
182 doc.application_annotations["Ubuntu One"]["private_application_annotations"] &&
183 doc.application_annotations["Ubuntu One"]["private_application_annotations"]["deleted"]) {
184 emit(doc._id, 1);
185 } else {
186 emit(doc._id, 0)
187 }
188 }
189 }"""
190 db.add_view("ubuntu_one_pair_record", map_js, None,
191 "ubuntu_one_pair_record")
192 results = db.execute_view("ubuntu_one_pair_record",
193 "ubuntu_one_pair_record")
194 found = False
195 # Results should contain either one row or no rows
196 # If there is one row, its value will be 0, meaning that there is
197 # already an Ubuntu One pairing record, or 1, meaning that there
198 # was an Ubuntu One pairing record but it has since been unpaired
199 # Only create a new record if there is not one already. Specifically,
200 # do not add the record if there is a deleted one, as this means
201 # that the user explicitly unpaired it!
202 for row in results:
203 found = True
204 if row.value == 1:
205 logger.debug(_("Not adding desktopcouch pairing since"
206 " the user has explicitly unpaired with Ubuntu One"))
207 else:
208 logger.debug(_("Not adding desktopcouch pairing since"
209 " we are already paired"))
210 if not found:
211 put_static_paired_service(None, "ubuntuone")
212 logger.debug(_("Pairing desktopcouch with Ubuntu One"))
213
214 def main(self):
215 """Starts the gtk main loop."""
216 self._connect_dbus_signals()
217
218 from twisted.internet import reactor
219 reactor.run()
220
221
222if __name__ == "__main__":
223 gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
224 gettext.textdomain(clientdefs.GETTEXT_PACKAGE)
225
226 # Register DBus service for making sure we run only one instance
227 bus = dbus.SessionBus()
228 if bus.request_name(DBUS_IFACE_AUTH_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
229 print _("Ubuntu One login manager already running, quitting")
230 sys.exit(0)
231
232 from twisted.internet import gtk2reactor
233 gtk2reactor.install()
234
235 login = Login(dbus.service.BusName(DBUS_IFACE_AUTH_NAME,
236 bus=dbus.SessionBus()))
237 manager = LoginMain()
238 manager.main()
2390
=== modified file 'bin/ubuntuone-preferences'
--- bin/ubuntuone-preferences 2010-06-11 16:17:34 +0000
+++ bin/ubuntuone-preferences 2010-06-16 20:35:34 +0000
@@ -33,6 +33,8 @@
33import subprocess33import subprocess
34from threading import Thread34from threading import Thread
35from oauth import oauth35from oauth import oauth
36
37from ubuntu_sso import DBUS_IFACE_AUTH_NAME, DBUS_PATH_AUTH
36from ubuntuone import clientdefs38from ubuntuone import clientdefs
37from ubuntuone.syncdaemon.tools import SyncDaemonTool39from ubuntuone.syncdaemon.tools import SyncDaemonTool
38from ubuntuone.api.restclient import RestClient40from ubuntuone.api.restclient import RestClient
@@ -68,9 +70,6 @@
68DBUS_IFACE_CONFIG_NAME = DBUS_IFACE_NAME + ".Config"70DBUS_IFACE_CONFIG_NAME = DBUS_IFACE_NAME + ".Config"
69DBUS_IFACE_STATUS_NAME = DBUS_IFACE_NAME + ".Status"71DBUS_IFACE_STATUS_NAME = DBUS_IFACE_NAME + ".Status"
7072
71DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
72DBUS_IFACE_AUTH_PATH = "/"
73
74# Our own DBus interface73# Our own DBus interface
75PREFS_BUS_NAME = "com.ubuntuone.Preferences"74PREFS_BUS_NAME = "com.ubuntuone.Preferences"
7675
@@ -104,7 +103,7 @@
104 """Make a login request to the login handling daemon."""103 """Make a login request to the login handling daemon."""
105 try:104 try:
106 client = bus.get_object(DBUS_IFACE_AUTH_NAME,105 client = bus.get_object(DBUS_IFACE_AUTH_NAME,
107 DBUS_IFACE_AUTH_PATH,106 DBUS_PATH_AUTH,
108 follow_name_owner_changes=True)107 follow_name_owner_changes=True)
109 iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)108 iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
110 iface.login('https://ubuntuone.com', 'ubuntuone',109 iface.login('https://ubuntuone.com', 'ubuntuone',
@@ -474,7 +473,7 @@
474 if token == local.key:473 if token == local.key:
475 try:474 try:
476 client = self.bus.get_object(DBUS_IFACE_AUTH_NAME,475 client = self.bus.get_object(DBUS_IFACE_AUTH_NAME,
477 DBUS_IFACE_AUTH_PATH,476 DBUS_PATH_AUTH,
478 follow_name_owner_changes=True)477 follow_name_owner_changes=True)
479 iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)478 iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
480 iface.clear_token('https://ubuntuone.com', 'ubuntuone',479 iface.clear_token('https://ubuntuone.com', 'ubuntuone',
481480
=== modified file 'contrib/testing/testcase.py'
--- contrib/testing/testcase.py 2010-05-26 21:43:27 +0000
+++ contrib/testing/testcase.py 2010-06-16 20:35:34 +0000
@@ -25,7 +25,7 @@
25import shutil25import shutil
26import itertools26import itertools
2727
28from ubuntuone.oauthdesktop.main import Login, LoginProcessor28from ubuntu_sso.main import Login, LoginProcessor
29from ubuntuone.syncdaemon import (29from ubuntuone.syncdaemon import (
30 config,30 config,
31 action_queue,31 action_queue,
3232
=== modified file 'data/Makefile.am'
--- data/Makefile.am 2010-06-11 15:33:16 +0000
+++ data/Makefile.am 2010-06-16 20:35:34 +0000
@@ -7,10 +7,7 @@
77
8configdir = $(sysconfdir)/xdg/ubuntuone8configdir = $(sysconfdir)/xdg/ubuntuone
9config_in_files = logging.conf.in9config_in_files = logging.conf.in
10config_DATA = oauth_urls syncdaemon.conf $(config_in_files:.conf.in=.conf)10config_DATA = syncdaemon.conf $(config_in_files:.conf.in=.conf)
11
12oauthdir = $(configdir)/oauth_registration.d
13oauth_DATA = oauth_registration.d/ubuntuone
1411
15memenudir = $(datadir)/indicators/me12memenudir = $(datadir)/indicators/me
16memenu_in_files = ubuntuone.menu.in13memenu_in_files = ubuntuone.menu.in
@@ -45,7 +42,6 @@
4542
46servicedir = $(DBUS_SERVICES_DIR)43servicedir = $(DBUS_SERVICES_DIR)
47service_in_files = \44service_in_files = \
48 com.ubuntuone.Authentication.service.in \
49 com.ubuntuone.SyncDaemon.service.in45 com.ubuntuone.SyncDaemon.service.in
50service_DATA = $(service_in_files:.service.in=.service)46service_DATA = $(service_in_files:.service.in=.service)
5147
@@ -146,8 +142,7 @@
146 stamp-render \142 stamp-render \
147 $(config_DATA) \143 $(config_DATA) \
148 $(apport_DATA) \144 $(apport_DATA) \
149 $(crashdb_DATA) \145 $(crashdb_DATA)
150 oauth_registration.d
151146
152CLEANFILES = \147CLEANFILES = \
153 $(memenu_DATA) \148 $(memenu_DATA) \
154149
=== removed file 'data/com.ubuntuone.Authentication.service.in'
--- data/com.ubuntuone.Authentication.service.in 2009-12-23 19:44:00 +0000
+++ data/com.ubuntuone.Authentication.service.in 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1[D-BUS Service]
2Name=com.ubuntuone.Authentication
3Exec=@libexecdir@/ubuntuone-login
40
=== removed directory 'data/oauth_registration.d'
=== removed file 'data/oauth_registration.d/ubuntuone'
--- data/oauth_registration.d/ubuntuone 2010-03-31 16:12:39 +0000
+++ data/oauth_registration.d/ubuntuone 1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
1[storagefs-live]
2realm = https://ubuntuone.com
3consumer_key = ubuntuone
4exe_path = /usr/bin/python
5application_name = ubuntuone-syncdaemon
6
7[storagefs-local]
8realm = http://localhost
9consumer_key = ubuntuone
10exe_path = /usr/bin/python
11application_name = ubuntuone-syncdaemon
12
13[control-panel]
14realm = https://ubuntuone.com
15consumer_key = ubuntuone
16exe_path = /usr/bin/python
17application_name = ubuntuone-preferences
18
19[launcher]
20realm = https://ubuntuone.com
21consumer_key = ubuntuone
22exe_path = /usr/bin/python
23application_name = ubuntuone-launch
240
=== removed file 'data/oauth_urls'
--- data/oauth_urls 2009-05-12 13:36:05 +0000
+++ data/oauth_urls 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1[default]
2request_token_url = /oauth/request/
3user_authorisation_url = /oauth/authorize/
4access_token_url = /oauth/access/
5consumer_secret=hammertime
60
=== removed directory 'tests/oauthdesktop'
=== removed file 'tests/oauthdesktop/__init__.py'
--- tests/oauthdesktop/__init__.py 2009-05-12 13:36:05 +0000
+++ tests/oauthdesktop/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,14 +0,0 @@
1# Copyright 2009 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14"""Tests for OAuth library"""
150
=== removed file 'tests/oauthdesktop/test_auth.py'
--- tests/oauthdesktop/test_auth.py 2010-01-11 17:46:00 +0000
+++ tests/oauthdesktop/test_auth.py 1970-01-01 00:00:00 +0000
@@ -1,263 +0,0 @@
1# test_auth - Tests for ubuntuone.oauthdesktop.auth module
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Tests for the OAuth client code for StorageFS."""
19
20import gnomekeyring
21from contrib.mocker import ANY, IN, MockerTestCase
22import testresources
23
24from oauth import oauth
25from twisted.trial.unittest import TestCase as TwistedTestCase
26from ubuntuone.oauthdesktop.auth import (AuthorisationClient,
27 NoAccessToken)
28
29
30# Adding TwistedTestCase as parent to be able to skip
31# test_ensure_access_token_no_token
32class AuthorisationClientTests(MockerTestCase, TwistedTestCase):
33 """Test the GNOME keyring integration portions of the auth code."""
34
35 def setUp(self):
36 """Sets up a mock keyring."""
37 MockerTestCase.setUp(self)
38 self.keyring = self.mocker.mock()
39 self.client = None
40 self.item = self.mocker.mock(gnomekeyring.Found)
41
42 self.item_id = 999
43
44 ex = self.expect(self.item.item_id)
45 ex.result(self.item_id)
46 ex.count(0, None)
47
48 ex = self.expect(self.item.secret)
49 ex.result('oauth_token=access_key&oauth_token_secret=access_secret')
50 ex.count(0, None)
51
52 def expect_token_query(self):
53 """Expects the keyring to be queried for a token."""
54 return self.expect(
55 self.keyring.find_items_sync(
56 gnomekeyring.ITEM_GENERIC_SECRET,
57 {'ubuntuone-realm': 'realm',
58 'oauth-consumer-key': 'consumer_key'})
59 )
60
61 def expect_token_store(self):
62 """Expects the token to be stored in the keyring."""
63 return self.expect(self.keyring.item_create_sync(
64 None, gnomekeyring.ITEM_GENERIC_SECRET,
65 'UbuntuOne token for realm',
66 {'ubuntuone-realm': 'realm',
67 'oauth-consumer-key': 'consumer_key'},
68 # Either order for the token and secret is valid
69 IN(['oauth_token=access_key&oauth_token_secret=access_secret',
70 'oauth_token_secret=access_secret&oauth_token=access_key']),
71 True))
72
73 def expect_token_store_denied(self):
74 """Expects the token to be denied for storing in keyring."""
75 self.expect_token_store().throw(gnomekeyring.DeniedError)
76
77 def mock_has_token(self):
78 """Mocks a cached token in the keyring."""
79 self.expect_token_query().result([self.item])
80
81 def mock_no_token(self, exception):
82 """Mocks no token in the keyring."""
83 self.expect_token_query().throw(exception)
84
85 def replay(self, callback_parent=None, callback_denied=None,
86 do_login=True):
87 """Starts the replay phase and sets up a client object to be tested,
88 wired up to the mock keyring.
89
90 """
91 self.mocker.replay()
92 self.client = AuthorisationClient(
93 'realm', 'request_token_url', 'user_authorisation_url',
94 'access_token_url', 'consumer_key', 'consumer_secret',
95 callback_parent, callback_denied, do_login, keyring=self.keyring)
96
97 def test_get_access_token(self):
98 """The get_access_token method returns the access token"""
99 self.mock_has_token()
100 self.replay()
101 token = self.client.get_access_token()
102 self.assertTrue(isinstance(token, oauth.OAuthToken))
103 self.assertEqual(token.key, 'access_key')
104 self.assertEqual(token.secret, 'access_secret')
105
106 def test_get_access_token_no_match(self):
107 """The get_access_token method fails if there are no matching items"""
108 self.mock_no_token(gnomekeyring.NoMatchError)
109 self.replay()
110 self.assertRaises(NoAccessToken, self.client.get_access_token)
111
112 def test_get_access_token_denied(self):
113 """The get_access_token method fails if access is denied"""
114 self.mock_no_token(gnomekeyring.DeniedError)
115 self.replay()
116 self.assertRaises(NoAccessToken, self.client.get_access_token)
117
118 def test_have_access_token(self):
119 """The `have_access_token` method returns True if a the
120 keyring contains a token."""
121 self.mock_has_token()
122 self.replay()
123 self.assertEqual(self.client.have_access_token(), True)
124
125 def test_have_access_token_fail(self):
126 """The `have_access_token` method returns False if the keyring
127 does not contain a token."""
128 self.mock_no_token(gnomekeyring.NoMatchError)
129 self.replay()
130 self.assertEqual(self.client.have_access_token(), False)
131
132 def test_store_token(self):
133 """The store_token method correctly stores an item in the keyring,
134 and correctly sets an ACL on it."""
135 self.expect_token_store().result(self.item_id)
136 saka = self.mocker.replace(
137 "ubuntuone.oauthdesktop.key_acls.set_all_key_acls")
138 saka(item_id=self.item_id)
139 self.mocker.result(None)
140
141 sleep = self.mocker.replace("time.sleep")
142 sleep(4)
143 self.mocker.result(None)
144
145 self.replay()
146 self.client.store_token(oauth.OAuthToken('access_key', 'access_secret'))
147
148 def test_store_token_denied(self):
149 """The store_token method correctly stores an item in the keyring,
150 and correctly sets an ACL on it."""
151 self.expect_token_store_denied()
152
153 self.replay()
154 self.client.store_token(oauth.OAuthToken('access_key', 'access_secret'))
155
156 def test_clear_existing_token(self):
157 """Makes sure that clear token clears an existing token."""
158 self.mock_has_token()
159 self.expect(self.keyring.item_delete_sync(None, self.item_id))
160 self.replay()
161 self.client.clear_token()
162
163 def test_clear_no_existing_token(self):
164 """Makes sure that clear with no existing token still works."""
165 self.mock_no_token(gnomekeyring.NoMatchError)
166 self.replay()
167 self.client.clear_token()
168
169 def test_ensure_access_token(self):
170 """If the user already has a token, no new token is requested."""
171 self.mock_has_token()
172 callback_function = self.mocker.mock()
173 callback_function(ANY)
174 self.replay(callback_parent=callback_function)
175 self.client.ensure_access_token()
176
177 def test_ensure_access_token_no_token(self):
178 """If the user has no token, a new one is requested and stored, via
179 an OAuth callback to the internal webserver."""
180
181 self.mock_no_token(gnomekeyring.NoMatchError)
182
183 request_token = self.mocker.mock()
184 self.expect(request_token.key).result('access_key').count(0, None)
185 ex = self.expect(request_token.secret)
186 ex.result('access_secret').count(0, None)
187 make_token_request = self.mocker.mock()
188 self.expect(make_token_request(ANY)).result(request_token)
189
190 open_in_browser = self.mocker.mock()
191 open_in_browser(ANY)
192
193 ri = self.mocker.replace("random.randint")
194 ri(1000000, 10000000)
195 self.mocker.result(12345678)
196 get_temporary_httpd = self.mocker.mock()
197 get_temporary_httpd(12345678, ANY, True)
198 self.mocker.result("http://callbackurl")
199
200 self.replay(callback_parent=lambda a: None)
201
202 self.client.make_token_request = make_token_request
203 self.client.open_in_browser = open_in_browser
204 self.client.get_temporary_httpd = get_temporary_httpd
205 # skip the "are we online, via networkmanager" bit
206 self.client.acquire_access_token_if_online = \
207 self.client.acquire_access_token
208 self.client.ensure_access_token()
209 test_ensure_access_token_no_token.skip = \
210 "Fails with exceptions.AssertionError: [Mocker] Unmet expectations, "\
211 "see bug #488933"
212
213
214class AcquireAccessTokenTests(testresources.ResourcedTestCase):
215 """OAuth token acquisition tests."""
216
217 def setUp(self):
218 super(AcquireAccessTokenTests, self).setUp()
219
220 def tearDown(self):
221 super(AcquireAccessTokenTests, self).tearDown()
222
223 def test_acquire_access_token(self):
224 """Test that acquire_access_token() can acquire the access token."""
225
226 def make_token_request(oauth_request):
227 """Make an OAuth token request via the test browser."""
228 return oauth.OAuthToken.from_string("oauth_token=access_token&" +
229 "oauth_token_secret=" +
230 "access_secret")
231
232 def open_in_browser(url):
233 """Just return as we aren't subscribed to the page."""
234 return
235
236 def got_token(token):
237 """Called with the token once auth is completed"""
238 self.assertTrue(isinstance(token, oauth.OAuthToken))
239
240 def get_temporary_httpd(nonce, retrieve_function, store):
241 """Mock the temporary httpd and return a callback URL"""
242 # returns callback URL; this is an invalid URL, of course,
243 # (port is too high) but we check later that the mechanize
244 # browser tries to navigate there
245 return "http://localhost:99999/?nonce=99999"
246
247 def store_token(token):
248 """Don't use the keyring; do nothing"""
249 pass
250
251 client = AuthorisationClient(
252 'http://ubuntuone/',
253 'http://ubuntuone/oauth/request/',
254 'http://ubuntuone/oauth/authorize/',
255 'http://ubuntuone/oauth/access/',
256 'consumer_key', 'consumer_secret',
257 callback_parent=got_token)
258 client.make_token_request = make_token_request
259 client.open_in_browser = open_in_browser
260 client.get_temporary_httpd = get_temporary_httpd
261 client.store_token = store_token
262
263 client.acquire_access_token('token description')
2640
=== removed file 'tests/oauthdesktop/test_config.py'
--- tests/oauthdesktop/test_config.py 2009-07-02 20:22:51 +0000
+++ tests/oauthdesktop/test_config.py 1970-01-01 00:00:00 +0000
@@ -1,90 +0,0 @@
1# test_config - tests for ubuntuone.oauthdesktop.config
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Tests for the OAuth client code for StorageFS."""
19
20import os, StringIO
21from contrib.mocker import MockerTestCase
22from ubuntuone.oauthdesktop import config
23from ubuntuone.oauthdesktop.config import get_config
24
25class ConfigFileTests(MockerTestCase):
26 """Test the config file finder"""
27
28 def test_get_file_from_tmp(self):
29 """Does the tmp file get chosen?"""
30
31 # mock that tmp config file exists
32 tmp_config_file = os.path.realpath(os.path.join(
33 os.path.split(config.__file__)[0], "../../data/oauth_urls"
34 ))
35 osp = self.mocker.replace("os.path")
36 osp.isfile(tmp_config_file)
37 self.mocker.result(True)
38 self.mocker.replay()
39
40 conf = get_config()
41 self.assertEqual(conf.FILENAME, tmp_config_file)
42
43 def test_get_file_from_home(self):
44 """Does our home config file get chosen?"""
45
46 # mock that home config file exists
47 home_config_file = os.path.expanduser("~/.config/ubuntuone/oauth_urls")
48 osp = self.mocker.replace("os.path")
49 osp.exists(home_config_file)
50 self.mocker.result(True)
51 self.mocker.replay()
52
53 conf = get_config(use_tmpconfig=False) # pretend not in source tree
54 self.assertEqual(conf.FILENAME, home_config_file)
55
56 def test_get_file_from_etc(self):
57 """Does our /etc config file get chosen?"""
58
59 # mock that etc config file exists
60 etc_config_file = os.path.expanduser("/etc/xdg/ubuntuone/oauth_urls")
61 osp = self.mocker.replace("os.path")
62 osp.exists(etc_config_file)
63 self.mocker.result(True)
64 self.mocker.replay()
65
66 conf = get_config(use_tmpconfig=False) # pretend not in source tree
67 self.assertEqual(conf.FILENAME, etc_config_file)
68
69 def test_tmp_file_parses(self):
70 """Does the tmp file get chosen and parse correctly?"""
71
72 # mock that tmp config file exists
73 tmp_config_file = os.path.realpath(os.path.join(
74 os.path.split(config.__file__)[0], "../../data/oauth_urls"
75 ))
76 osp = self.mocker.replace("os.path")
77 osp.isfile(tmp_config_file)
78 self.mocker.result(True)
79
80 sio = StringIO.StringIO("[default]\n")
81 mock_open = self.mocker.replace(open)
82 mock_open(tmp_config_file)
83 self.mocker.result(sio)
84
85 self.mocker.replay()
86
87 conf = get_config()
88 self.assertTrue(conf.has_section("default"))
89
90
910
=== removed file 'tests/oauthdesktop/test_key_acls.py'
--- tests/oauthdesktop/test_key_acls.py 2009-06-26 17:01:42 +0000
+++ tests/oauthdesktop/test_key_acls.py 1970-01-01 00:00:00 +0000
@@ -1,181 +0,0 @@
1# test_key_acls - tests for ubuntuone.oauthdesktop.key_acls
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Tests for the ACL setting code for ubuntuone-oauth-login."""
19
20import os, StringIO
21import xdg.BaseDirectory
22from contrib.mocker import MockerTestCase, IN, ANY
23from ubuntuone.oauthdesktop.key_acls import (
24 get_privileged_config_folder, get_acl_preregistrations, set_all_key_acls,
25 set_single_acl)
26
27class Loader(MockerTestCase):
28 """Confirm only the /etc/xdg config files are loaded"""
29
30 def test_etc_found(self):
31 """Is the /etc/xdg folder found if present?"""
32
33 ETC_FOLDER = "/etc/xdg/ubuntuone"
34 osp = self.mocker.replace("os.path")
35 osp.exists(ETC_FOLDER)
36 self.mocker.result(True)
37 self.mocker.replay()
38
39 self.assertEqual(ETC_FOLDER,
40 get_privileged_config_folder(use_source_tree_folder=False))
41
42 def test_etc_found_despite_home(self):
43 """Is the /etc/xdg folder found even if the $HOME folder is present?"""
44
45 ETC_FOLDER = "/etc/xdg/ubuntuone"
46 HOME_FOLDER = os.path.join(xdg.BaseDirectory.xdg_config_home,
47 "ubuntuone")
48 osp = self.mocker.replace("os.path")
49 osp.exists(ETC_FOLDER)
50 self.mocker.result(True)
51 osp.exists(HOME_FOLDER)
52 self.mocker.result(True)
53 self.mocker.replay()
54
55 self.assertEqual(ETC_FOLDER,
56 get_privileged_config_folder(use_source_tree_folder=False))
57
58 def test_etc_files_found(self):
59 """Are files in /etc found?"""
60 ETC_FOLDER = "/etc/xdg/ubuntuone"
61 ETC_FILES_FOLDER = "/etc/xdg/ubuntuone/oauth_registration.d"
62 FILE_LIST = ["a", "b"]
63 osp = self.mocker.replace("os.path")
64 osp.exists(ETC_FOLDER)
65 self.mocker.result(True)
66 osp.isdir(ETC_FILES_FOLDER)
67 self.mocker.result(True)
68 listdir = self.mocker.replace("os.listdir")
69 listdir(ETC_FILES_FOLDER)
70 self.mocker.result(FILE_LIST)
71 self.mocker.replay()
72
73 our_list = sorted(
74 [os.path.join(ETC_FILES_FOLDER, x) for x in FILE_LIST])
75 their_list = sorted(
76 get_acl_preregistrations(use_source_tree_folder=False))
77 self.assertEqual(our_list, their_list)
78
79 def test_set_single_acl(self):
80 """Does set_single_acl work?"""
81
82 giifr = self.mocker.replace(
83 "ubuntuone.oauthdesktop.key_acls.get_item_ids_for_realm")
84 igas = self.mocker.replace("gnomekeyring.item_get_acl_sync")
85 isas = self.mocker.replace("gnomekeyring.item_set_acl_sync")
86 acon = self.mocker.replace("gnomekeyring.AccessControl")
87 self.expect(giifr("realm", "consumer_key")).result([999])
88 self.expect(igas(None, 999)).result([])
89 ac1 = self.mocker.mock()
90 self.expect(acon(ANY, ANY)).result(ac1)
91 ac1.set_display_name("application_name")
92 ac1.set_path_name("/tmp/exe_path")
93 self.expect(isas(None, 999, [ac1])).result(None)
94 self.expect(giifr("realm2", "consumer_key2")).result([9999])
95 self.expect(igas(None, 9999)).result([])
96 ac2 = self.mocker.mock()
97 self.expect(acon(ANY, ANY)).result(ac2)
98 ac2.set_display_name("application_name2")
99 ac2.set_path_name("/tmp/exe_path2")
100 self.expect(isas(None, 9999, [ac2])).result(None)
101 self.mocker.replay()
102
103 set_single_acl([
104 ("realm", "consumer_key", "/tmp/exe_path", "application_name"),
105 ("realm2", "consumer_key2", "/tmp/exe_path2", "application_name2"),
106 ])
107
108
109 def test_etc_files_parsed(self):
110 """Are files in /etc parsed correctly?"""
111
112 ETC_FOLDER = "/etc/xdg/ubuntuone"
113 ETC_FILES_FOLDER = "/etc/xdg/ubuntuone/oauth_registration.d"
114 FILE_LIST = ["a", "b"]
115 osp = self.mocker.replace("os.path")
116 osp.exists(ETC_FOLDER)
117 self.mocker.result(True)
118 osp.isdir(ETC_FILES_FOLDER)
119 self.mocker.result(True)
120 listdir = self.mocker.replace("os.listdir")
121 listdir(ETC_FILES_FOLDER)
122 self.mocker.result(FILE_LIST)
123
124 sio1 = StringIO.StringIO("""[app1]
125realm = https://realm.example.com/
126consumer_key = example_key
127exe_path = /nowhere/executable/path
128application_name = example_app_name
129
130[app2]
131realm = https://other.example.com/
132consumer_key = example_key
133exe_path = /nowhere/executable/path
134application_name = example_app_name
135
136""")
137 sio2 = StringIO.StringIO("""[app3]
138exe_path = /nowhere/path/2
139application_name = example_app_name2
140consumer_key = example_key2
141realm = https://realm2.example.com/
142""")
143 mock_open = self.mocker.replace(open)
144 mock_open(os.path.join(ETC_FILES_FOLDER, "a"))
145 self.mocker.result(sio1)
146 mock_open(os.path.join(ETC_FILES_FOLDER, "b"))
147 self.mocker.result(sio2)
148
149 ssa = self.mocker.replace(
150 "ubuntuone.oauthdesktop.key_acls.set_single_acl")
151 # list may come up in any order
152 ssa(IN([
153 [
154 ("https://realm.example.com/", "example_key",
155 "/nowhere/executable/path", "example_app_name"),
156 ("https://other.example.com/", "example_key",
157 "/nowhere/executable/path", "example_app_name"),
158 ],
159 [
160 ("https://other.example.com/", "example_key",
161 "/nowhere/executable/path", "example_app_name"),
162 ("https://realm.example.com/", "example_key",
163 "/nowhere/executable/path", "example_app_name"),
164 ],
165 ]), specific_item_id=None
166 )
167 self.mocker.result(None)
168 ssa([
169 ("https://realm2.example.com/", "example_key2",
170 "/nowhere/path/2", "example_app_name2")
171 ], specific_item_id=None)
172 self.mocker.result(None)
173 self.mocker.replay()
174
175 set_all_key_acls(use_source_tree_folder=False)
176
177
178
179
180
181
1820
=== removed file 'tests/oauthdesktop/test_main.py'
--- tests/oauthdesktop/test_main.py 2009-06-26 17:01:42 +0000
+++ tests/oauthdesktop/test_main.py 1970-01-01 00:00:00 +0000
@@ -1,93 +0,0 @@
1# test_main - tests for ubuntuone.oauthdesktop.main
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Tests for the OAuth client code for StorageFS."""
19
20import os, StringIO
21from contrib.mocker import MockerTestCase
22from ubuntuone.oauthdesktop import config
23from ubuntuone.oauthdesktop.main import LoginProcessor, BadRealmError
24
25class Realm(MockerTestCase):
26 """Test the realm handling finder"""
27
28 def test_invalid_realm(self):
29 """Are invalid realms rejected?"""
30
31 login = LoginProcessor(None, use_libnotify=False)
32 self.assertRaises(BadRealmError, login.login, "bad realm", "key")
33
34 def test_realms(self):
35 """Are realm URLs correctly retrieved from the config?"""
36
37 # mock that tmp config file exists
38 tmp_config_file = os.path.realpath(os.path.join(
39 os.path.split(config.__file__)[0], "../../data/oauth_urls"
40 ))
41 osp = self.mocker.replace("os.path")
42 osp.isfile(tmp_config_file)
43 self.mocker.result(True)
44
45 sio = StringIO.StringIO("""[default]
46request_token_url = /rtu-default
47user_authorisation_url = /uau-default
48access_token_url = /atu-default
49consumer_secret = foo-default
50
51[http://localhost]
52request_token_url = /rtu-localhost
53user_authorisation_url = /uau-localhost
54access_token_url = /atu-localhost
55consumer_secret = foo-localhost
56
57[http://ubuntuone.com]
58request_token_url = /rtu-ubuntuone
59user_authorisation_url = /uau-ubuntuone
60access_token_url = /atu-ubuntuone
61consumer_secret = foo-ubuntuone
62
63""")
64 mock_open = self.mocker.replace(open)
65 mock_open(tmp_config_file)
66 self.mocker.result(sio)
67
68 self.mocker.replay()
69
70 login = LoginProcessor(None, use_libnotify=False)
71
72 # are localhost:XXXX URLs correctly fetched?
73 (rtu, uau, atu, cs) = login.get_config_urls("http://localhost:9876")
74 self.assertEqual(rtu, "http://localhost:9876/rtu-localhost")
75 self.assertEqual(uau, "http://localhost:9876/uau-localhost")
76 self.assertEqual(atu, "http://localhost:9876/atu-localhost")
77 self.assertEqual(cs, "foo-localhost")
78
79 # is a URL specifically in the config correctly fetched?
80 (rtu, uau, atu, cs) = login.get_config_urls("http://ubuntuone.com")
81 self.assertEqual(rtu, "http://ubuntuone.com/rtu-ubuntuone")
82 self.assertEqual(uau, "http://ubuntuone.com/uau-ubuntuone")
83 self.assertEqual(atu, "http://ubuntuone.com/atu-ubuntuone")
84 self.assertEqual(cs, "foo-ubuntuone")
85
86 # is a URL not in the config correctly fetched as default?
87 (rtu, uau, atu, cs) = login.get_config_urls("http://other.example.net")
88 self.assertEqual(rtu, "http://other.example.net/rtu-default")
89 self.assertEqual(uau, "http://other.example.net/uau-default")
90 self.assertEqual(atu, "http://other.example.net/atu-default")
91 self.assertEqual(cs, "foo-default")
92
93
940
=== removed file 'tests/test_login.py'
--- tests/test_login.py 2010-01-15 16:32:12 +0000
+++ tests/test_login.py 1970-01-01 00:00:00 +0000
@@ -1,187 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Author: Rodney Dawes <rodney.dawes@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18""" Tests for the ubuntuone-login script """
19
20import dbus.service
21import new
22import os
23
24from contrib.testing.testcase import DBusTwistedTestCase, FakeLogin
25from twisted.internet import defer
26from twisted.python.failure import Failure
27from ubuntuone.syncdaemon import dbus_interface
28
29class InvalidSignalError(Exception):
30 """Exception for when we get the wrong signal called."""
31 pass
32
33class LoginTests(DBusTwistedTestCase):
34 """Basic tests for the ubuntuone-login script """
35
36 _path = os.path.join(os.getcwd(), "bin", "ubuntuone-login")
37 u1login = new.module('u1login')
38 execfile(_path, u1login.__dict__)
39
40 def setUp(self):
41 DBusTwistedTestCase.setUp(self)
42 self.oauth = FakeLogin(self.bus)
43 self._old_path = dbus_interface.DBUS_PATH_AUTH
44 dbus_interface.DBUS_PATH_AUTH = '/oauthdesktop'
45
46 def tearDown(self):
47 # collect all signal receivers registered during the test
48 signal_receivers = set()
49 with self.bus._signals_lock:
50 for group in self.bus._signal_recipients_by_object_path.values():
51 for matches in group.values():
52 for match in matches.values():
53 signal_receivers.update(match)
54 d = self.cleanup_signal_receivers(signal_receivers)
55 def shutdown(r):
56 self.oauth.shutdown()
57 dbus_interface.DBUS_PATH_AUTH = self._old_path
58 d.addBoth(shutdown)
59 d.addBoth(lambda _: DBusTwistedTestCase.tearDown(self))
60 return d
61
62 def test_new_credentials(self):
63 """ Test logging in """
64 def new_creds(realm=None, consumer_key=None, sender=None):
65 """ Override the callback """
66 d.callback(True)
67
68 def auth_denied():
69 """ Override the callback """
70 d.errback(Failure(InvalidSignalError()))
71
72 def got_oauth_error(message=None):
73 """ Override the callback """
74 d.errback(Failure(InvalidSignalError()))
75
76 def set_up_desktopcouch_pairing(consumer_key):
77 """ Override the method """
78 return
79
80 def main():
81 """ Override LoginMain.main """
82 return
83
84 login = self.u1login.LoginMain()
85 login.main = main
86 login.new_credentials = new_creds
87 login.auth_denied = auth_denied
88 login.got_oauth_error = got_oauth_error
89 login.set_up_desktopcouch_pairing = set_up_desktopcouch_pairing
90
91 login._connect_dbus_signals()
92
93 client = self.bus.get_object(self.u1login.DBUS_IFACE_AUTH_NAME,
94 '/oauthdesktop',
95 follow_name_owner_changes=True)
96 d = defer.Deferred()
97
98 def login_handler():
99 """ login handler """
100 return
101
102 iface = dbus.Interface(client, self.u1login.DBUS_IFACE_AUTH_NAME)
103 iface.login('http://localhost', self.u1login.OAUTH_CONSUMER,
104 reply_handler=login_handler,
105 error_handler=self.error_handler)
106 return d
107
108 def test_auth_denied(self):
109 """ Test that denying authorization works correctly. """
110
111 def new_creds(realm=None, consumer_key=None, sender=None):
112 """ Override the callback """
113 d.errback(Failure(InvalidSignalError()))
114
115 def auth_denied():
116 """ Override the callback """
117 d.callback(True)
118
119 def got_oauth_error(message=None):
120 """ override the callback """
121 d.errback(Failure(InvalidSignalError()))
122
123 login = self.u1login.LoginMain()
124 login.main = self.main
125 login.new_credentials = new_creds
126 login.auth_denied = auth_denied
127 login.got_oauth_error = got_oauth_error
128
129 login._connect_dbus_signals()
130
131 self.oauth.processor.next_login_with(self.oauth.processor.got_denial)
132
133 client = self.bus.get_object(self.u1login.DBUS_IFACE_AUTH_NAME,
134 '/oauthdesktop',
135 follow_name_owner_changes=True)
136 d = defer.Deferred()
137
138 def login_handler():
139 """ login handler """
140 return
141
142 iface = dbus.Interface(client, self.u1login.DBUS_IFACE_AUTH_NAME)
143 iface.login('http://localhost', self.u1login.OAUTH_CONSUMER,
144 reply_handler=login_handler,
145 error_handler=self.error_handler)
146 return d
147
148 def test_oauth_error(self):
149 """ Test that getting an error works correctly. """
150
151 def new_creds(realm=None, consumer_key=None, sender=None):
152 """ Override the callback """
153 d.errback(Failure(InvalidSignalError()))
154
155 def auth_denied():
156 """ Override the callback """
157 d.errback(Failure(InvalidSignalError()))
158
159 def got_oauth_error(message=None):
160 """ override the callback """
161 d.callback(True)
162
163 login = self.u1login.LoginMain()
164 login.main = self.main
165 login.new_credentials = new_creds
166 login.auth_denied = auth_denied
167 login.got_oauth_error = got_oauth_error
168
169 login._connect_dbus_signals()
170
171 self.oauth.processor.next_login_with(self.oauth.processor.got_error,
172 ('error!',))
173
174 client = self.bus.get_object(self.u1login.DBUS_IFACE_AUTH_NAME,
175 '/oauthdesktop',
176 follow_name_owner_changes=True)
177 d = defer.Deferred()
178
179 def login_handler():
180 """ login handler """
181 return
182
183 iface = dbus.Interface(client, self.u1login.DBUS_IFACE_AUTH_NAME)
184 iface.login('http://localhost', self.u1login.OAUTH_CONSUMER,
185 reply_handler=login_handler,
186 error_handler=self.error_handler)
187 return d
1880
=== modified file 'tests/test_preferences.py'
--- tests/test_preferences.py 2010-06-15 09:16:48 +0000
+++ tests/test_preferences.py 2010-06-16 20:35:34 +0000
@@ -498,3 +498,5 @@
498 error_handler=got_dbus_error)498 error_handler=got_dbus_error)
499499
500 return d500 return d
501 test_login_check.skip = \
502 "This shouldn't depend on the real DBus service (see bug #591340)."
501503
=== removed directory 'ubuntuone/oauthdesktop'
=== removed file 'ubuntuone/oauthdesktop/__init__.py'
--- ubuntuone/oauthdesktop/__init__.py 2009-06-26 17:01:42 +0000
+++ ubuntuone/oauthdesktop/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1# ubuntuone.oauthdesktop - OAuth client support for desktop apps
2#
3# Copyright 2009 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16"""OAuth client authorisation code."""
170
=== removed file 'ubuntuone/oauthdesktop/auth.py'
--- ubuntuone/oauthdesktop/auth.py 2010-02-10 17:35:26 +0000
+++ ubuntuone/oauthdesktop/auth.py 1970-01-01 00:00:00 +0000
@@ -1,480 +0,0 @@
1# ubuntuone.oauthdesktop.auth - Client authorization module
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""OAuth client authorisation code.
19
20This code handles acquisition of an OAuth access token for a service,
21managed through the GNOME keyring for future use, and asynchronously.
22"""
23
24__metaclass__ = type
25
26import subprocess
27import random
28import dbus
29import os
30import socket, httplib, urllib
31
32import gnomekeyring
33from oauth import oauth
34try:
35 from ubuntuone.clientdefs import VERSION
36 ubuntuone_client_version = VERSION
37except ImportError:
38 ubuntuone_client_version = "Unknown"
39from ubuntuone.oauthdesktop.key_acls import set_all_key_acls
40
41from threading import Thread
42from twisted.internet import reactor
43from twisted.web import server, resource
44
45from ubuntuone.oauthdesktop.logger import setupLogging
46logger = setupLogging("UbuntuOne.OAuthDesktop.auth")
47
48
49class NoAccessToken(Exception):
50 """No access token available."""
51
52# NetworkManager State constants
53NM_STATE_UNKNOWN = 0
54NM_STATE_ASLEEP = 1
55NM_STATE_CONNECTING = 2
56NM_STATE_CONNECTED = 3
57NM_STATE_DISCONNECTED = 4
58
59# Monkeypatch httplib so that urllib will fail on invalid certificate
60# Only patch if we can import ssl to work around fail in 2.5
61try:
62 import ssl
63except ImportError:
64 pass
65else:
66 def _connect_wrapper(self):
67 """Override HTTPSConnection.connect to require certificate checks"""
68 sock = socket.create_connection((self.host, self.port), self.timeout)
69 try:
70 if self._tunnel_host:
71 self.sock = sock
72 self._tunnel()
73 except AttributeError:
74 pass
75 self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
76 cert_reqs=ssl.CERT_REQUIRED,
77 ca_certs="/etc/ssl/certs/ca-certificates.crt")
78 httplib.HTTPSConnection.connect = _connect_wrapper
79
80
81class FancyURLOpenerWithRedirectedPOST(urllib.FancyURLopener):
82 """FancyURLopener does not redirect postdata when redirecting POSTs"""
83 version = "Ubuntu One/Login (%s)" % ubuntuone_client_version
84 def redirect_internal(self, url, fp, errcode, errmsg, headers, data):
85 """Actually perform a redirect"""
86 # All the same as the original, except passing data, below
87 if 'location' in headers:
88 newurl = headers['location']
89 elif 'uri' in headers:
90 newurl = headers['uri']
91 else:
92 return
93 fp.read()
94 fp.close()
95 # In case the server sent a relative URL, join with original:
96 newurl = urllib.basejoin(self.type + ":" + url, newurl)
97
98 # pass data if present when we redirect
99 if data:
100 return self.open(newurl, data)
101 else:
102 return self.open(newurl)
103
104class AuthorisationClient(object):
105 """OAuth authorisation client."""
106 def __init__(self, realm, request_token_url, user_authorisation_url,
107 access_token_url, consumer_key, consumer_secret,
108 callback_parent, callback_denied=None,
109 callback_notoken=None, callback_error=None, do_login=True,
110 keyring=gnomekeyring):
111 """Create an `AuthorisationClient` instance.
112
113 @param realm: the OAuth realm.
114 @param request_token_url: the OAuth request token URL.
115 @param user_authorisation_url: the OAuth user authorisation URL.
116 @param access_token_url: the OAuth access token URL.
117 @param consumer_key: the OAuth consumer key.
118 @param consumer_secret: the OAuth consumer secret.
119 @param callback_parent: a function in the includer to call with a token
120
121 The preceding parameters are defined in sections 3 and 4.1 of the
122 OAuth Core 1.0 specification. The following parameters are not:
123
124 @param callback_denied: a function to call if no token is available
125 @param do_login: whether to create a token if one is not cached
126 @param keychain: the keyring object to use (defaults to gnomekeyring)
127
128 """
129 self.realm = realm
130 self.request_token_url = request_token_url
131 self.user_authorisation_url = user_authorisation_url
132 self.access_token_url = access_token_url
133 self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
134 self.callback_parent = callback_parent
135 self.callback_denied = callback_denied
136 self.callback_notoken = callback_notoken
137 self.callback_error = callback_error
138 self.do_login = do_login
139 self.request_token = None
140 self.saved_acquire_details = (None, None, None)
141 self.keyring = keyring
142 logger.debug("auth.AuthorisationClient created with parameters "+ \
143 "realm='%s', request_token_url='%s', user_authorisation_url='%s',"+\
144 "access_token_url='%s', consumer_key='%s', callback_parent='%s'",
145 realm, request_token_url, user_authorisation_url, access_token_url,
146 consumer_key, callback_parent)
147
148 def _get_keyring_items(self):
149 """Raw interface to obtain keyring items."""
150 return self.keyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET,
151 {'ubuntuone-realm': self.realm,
152 'oauth-consumer-key':
153 self.consumer.key})
154
155 def _forward_error_callback(self, error):
156 """Forward an error through callback_error()"""
157 if self.callback_error:
158 self.callback_error(str(error))
159 else:
160 raise error
161
162 def get_access_token(self):
163 """Get the access token from the keyring.
164
165 If no token is available in the keyring, `NoAccessToken` is raised.
166 """
167 logger.debug("Trying to fetch the token from the keyring")
168 try:
169 items = self._get_keyring_items()
170 except (gnomekeyring.NoMatchError,
171 gnomekeyring.DeniedError):
172 logger.debug("Access token was not in the keyring")
173 raise NoAccessToken("No access token found.")
174 logger.debug("Access token successfully found in the keyring")
175 return oauth.OAuthToken.from_string(items[0].secret)
176
177 def clear_token(self):
178 """Clear any stored tokens from the keyring."""
179 logger.debug("Searching keyring for existing tokens to delete.")
180 try:
181 items = self._get_keyring_items()
182 except (gnomekeyring.NoMatchError,
183 gnomekeyring.DeniedError):
184 logger.debug("No preexisting tokens found")
185 else:
186 logger.debug("Deleting %s tokens from the keyring" % len(items))
187 for item in items:
188 try:
189 self.keyring.item_delete_sync(None, item.item_id)
190 except gnomekeyring.DeniedError:
191 logger.debug("Permission denied deleting token")
192
193 def store_token(self, access_token):
194 """Store the given access token in the keyring.
195
196 The keyring item is identified by the OAuth realm and consumer
197 key to support multiple instances.
198 """
199 logger.debug("Trying to store the token in the keyring")
200 try:
201 item_id = self.keyring.item_create_sync(
202 None,
203 gnomekeyring.ITEM_GENERIC_SECRET,
204 'UbuntuOne token for %s' % self.realm,
205 {'ubuntuone-realm': self.realm,
206 'oauth-consumer-key': self.consumer.key},
207 access_token.to_string(),
208 True)
209 except gnomekeyring.DeniedError:
210 logger.debug("Permission denied storing token")
211 else:
212 # set ACLs on the key for all apps listed in xdg BaseDir, but only
213 # the root level one, not the user-level one
214 logger.debug("Setting ACLs on the token in the keyring")
215 set_all_key_acls(item_id=item_id)
216
217 # keyring seems to take a while to actually apply the change
218 # for when other people retrieve it, so sleep a bit.
219 # this ought to get fixed.
220 import time
221 time.sleep(4)
222
223 def have_access_token(self):
224 """Returns true if an access token is available from the keyring."""
225 try:
226 self.get_access_token()
227 except NoAccessToken:
228 return False
229 else:
230 return True
231
232 def make_token_request(self, oauth_request):
233 """Perform the given `OAuthRequest` and return the associated token."""
234
235 logger.debug("Making a token request")
236 # Note that we monkeypatched httplib above to handle invalid certs
237 # Ways this urlopen can fail:
238 # bad certificate
239 # raises IOError, e.args[1] == SSLError, e.args[1].errno == 1
240 # No such server
241 # raises IOError, e.args[1] == SSLError, e.args[1].errno == -2
242 try:
243 opener = FancyURLOpenerWithRedirectedPOST()
244 fp = opener.open(oauth_request.http_url, oauth_request.to_postdata())
245 data = fp.read()
246 except IOError, e:
247 self._forward_error_callback(e)
248 return
249
250 # we deliberately trap anything that might go wrong when parsing the
251 # token, because we do not want this to explicitly fail
252 # pylint: disable-msg=W0702
253 try:
254 out_token = oauth.OAuthToken.from_string(data)
255 logger.debug("Token successfully requested")
256 return out_token
257 except:
258 error = Exception(data)
259 logger.error("Token was not successfully retrieved: data was '%s'",
260 str(error))
261 self._forward_error_callback(error)
262
263 def open_in_browser(self, url):
264 """Open the given URL in the user's web browser."""
265 logger.debug("Opening '%s' in the browser", url)
266 p = subprocess.Popen(["xdg-open", url], bufsize=4096,
267 stderr=subprocess.PIPE)
268 p.wait()
269 if p.returncode != 0:
270 errors = "".join(p.stderr.readlines())
271 if errors != "":
272 self._forward_error_callback(IOError(errors))
273
274 def acquire_access_token_if_online(self, description=None, store=False):
275 """Check to see if we are online before trying to acquire"""
276 # Get NetworkManager state
277 logger.debug("Checking whether we are online")
278 try:
279 nm = dbus.SystemBus().get_object('org.freedesktop.NetworkManager',
280 '/org/freedesktop/NetworkManager',
281 follow_name_owner_changes=True)
282 except dbus.exceptions.DBusException:
283 logger.warn("Unable to connect to NetworkManager. Trying anyway.")
284 self.acquire_access_token(description, store)
285 else:
286 iface = dbus.Interface(nm, 'org.freedesktop.NetworkManager')
287
288 def got_state(state):
289 """Handler for when state() call succeeds."""
290 if state == NM_STATE_CONNECTED:
291 logger.debug("We are online")
292 self.acquire_access_token(description, store)
293 elif state == NM_STATE_CONNECTING:
294 logger.debug("We are currently going online")
295 # attach to NM's StateChanged signal
296 signal_match = nm.connect_to_signal(
297 signal_name="StateChanged",
298 handler_function=self.connection_established,
299 dbus_interface="org.freedesktop.NetworkManager")
300 # stash the details so the handler_function can get at them
301 self.saved_acquire_details = (signal_match, description,
302 store)
303 else:
304 # NM is not connected: fail
305 logger.debug("We are not online")
306
307 def got_error(error):
308 """Handler for D-Bus errors when calling state()."""
309 if error.get_dbus_name() == \
310 'org.freedesktop.DBus.Error.ServiceUnknown':
311 logger.debug("NetworkManager not available.")
312 self.acquire_access_token(description, store)
313 else:
314 logger.error("Error contacting NetworkManager: %s" % \
315 str(error))
316
317
318 iface.state(reply_handler=got_state, error_handler=got_error)
319
320 def connection_established(self, state):
321 """NetworkManager's state has changed, and we're watching for
322 a connection"""
323 logger.debug("Online status has changed to %s" % state)
324 if int(state) == NM_STATE_CONNECTED:
325 signal_match, description, store = self.saved_acquire_details
326 # disconnect the signal so we don't get called again
327 signal_match.remove()
328 # call the real acquire_access_token now it has a connection
329 logger.debug("Correctly connected: now starting auth process")
330 self.acquire_access_token(description, store)
331 else:
332 # connection changed but not to "connected", so keep waiting
333 logger.debug("Not yet connected: continuing to wait")
334
335 def acquire_access_token(self, description=None, store=False):
336 """Create an OAuth access token authorised against the user."""
337 signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
338
339 # Create a request token ...
340 logger.debug("Creating a request token to begin access request")
341 parameters = {}
342 if description:
343 parameters['description'] = description
344 # Add a nonce to the query so we know the callback (to our temp
345 # webserver) came from us
346 nonce = random.randint(1000000, 10000000)
347
348 # start temporary webserver to receive browser response
349 callback_url = self.get_temporary_httpd(nonce,
350 self.retrieve_access_token, store)
351
352 oauth_request = oauth.OAuthRequest.from_consumer_and_token(
353 callback=callback_url,
354 http_url=self.request_token_url,
355 oauth_consumer=self.consumer,
356 parameters=parameters)
357 oauth_request.sign_request(signature_method, self.consumer, None)
358 logger.debug("Making token request")
359 self.request_token = self.make_token_request(oauth_request)
360
361 # Request authorisation from the user
362 oauth_request = oauth.OAuthRequest.from_token_and_callback(
363 http_url=self.user_authorisation_url,
364 token=self.request_token)
365 nodename = os.uname()[1]
366 if nodename:
367 oauth_request.set_parameter("description", nodename)
368 Thread(target=self.open_in_browser, name="authorization",
369 args=(oauth_request.to_url(),)).start()
370
371 def get_temporary_httpd(self, nonce, retrieve_function, store):
372 "A separate class so it can be mocked in testing"
373 logger.debug("Creating a listening temp web server")
374 site = TemporaryTwistedWebServer(nonce=nonce,
375 retrieve_function=retrieve_function, store_yes_no=store)
376 temphttpd = server.Site(site)
377 temphttpdport = reactor.listenTCP(0, temphttpd)
378 callback_url = "http://localhost:%s/?nonce=%s" % (
379 temphttpdport.getHost().port, nonce)
380 site.set_port(temphttpdport)
381 logger.debug("Webserver listening on port '%s'", temphttpdport)
382 return callback_url
383
384 def retrieve_access_token(self, store=False, verifier=None):
385 """Retrieve the access token, once OAuth is done. This is a callback."""
386 logger.debug("Access token callback from temp webserver")
387 signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
388 oauth_request = oauth.OAuthRequest.from_consumer_and_token(
389 http_url=self.access_token_url,
390 oauth_consumer=self.consumer,
391 token=self.request_token)
392 oauth_request.set_parameter("oauth_verifier", verifier)
393 oauth_request.sign_request(
394 signature_method, self.consumer, self.request_token)
395 logger.debug("Retrieving access token from OAuth")
396 access_token = self.make_token_request(oauth_request)
397 if not access_token:
398 logger.error("Failed to get access token.")
399 if self.callback_denied is not None:
400 self.callback_denied()
401 else:
402 if store:
403 logger.debug("Storing access token in keyring")
404 self.store_token(access_token)
405 logger.debug("Calling the callback_parent")
406 self.callback_parent(access_token)
407
408 def ensure_access_token(self, description=None):
409 """Returns an access token, either from the keyring or newly acquired.
410
411 If a new token is acquired, it will be stored in the keyring
412 for future use.
413 """
414 try:
415 access_token = self.get_access_token()
416 self.callback_parent(access_token)
417 except NoAccessToken:
418 if self.do_login:
419 access_token = self.acquire_access_token_if_online(
420 description,
421 store=True)
422 else:
423 if self.callback_notoken is not None:
424 self.callback_notoken()
425
426
427class TemporaryTwistedWebServer(resource.Resource):
428 """A temporary httpd for the oauth process to call back to"""
429 isLeaf = True
430 def __init__(self, nonce, store_yes_no, retrieve_function):
431 """Initialize the temporary web server."""
432 resource.Resource.__init__(self)
433 self.nonce = nonce
434 self.store_yes_no = store_yes_no
435 self.retrieve_function = retrieve_function
436 reactor.callLater(600, self.stop) # ten minutes
437 self.port = None
438 def set_port(self, port):
439 """Save the Twisted port object so we can stop it later"""
440 self.port = port
441 def stop(self):
442 """Stop the httpd"""
443 logger.debug("Stopping temp webserver")
444 self.port.stopListening()
445 def render_GET(self, request):
446 """Handle incoming web requests"""
447 logger.debug("Incoming temp webserver hit received")
448 nonce = request.args.get("nonce", [None])[0]
449 url = request.args.get("return", ["https://one.ubuntu.com/"])[0]
450 verifier = request.args.get("oauth_verifier", [None])[0]
451 logger.debug("Got verifier %s" % verifier)
452 if nonce and (str(nonce) == str(self.nonce) and verifier):
453 self.retrieve_function(store=self.store_yes_no, verifier=verifier)
454 reactor.callLater(3, self.stop)
455 return """<!doctype html>
456 <html><head><meta http-equiv="refresh"
457 content="0;url=%(url)s">
458 </head>
459 <body>
460 <p>You should now automatically <a
461 href="%(url)s">return to %(url)s</a>.</p>
462 </body>
463 </html>
464 """ % { 'url' : url }
465 else:
466 self.retrieve_function(store=self.store_yes_no, verifier=verifier)
467 reactor.callLater(3, self.stop)
468 request.setResponseCode(400)
469 return """<!doctype html>
470 <html><head><title>Error</title></head>
471 <body>
472 <h1>There was an error</h1>
473 <p>The authentication process has not succeeded. This may be a
474 temporary problem; please try again in a few minutes.</p>
475 </body>
476 </html>
477 """
478
479
480
4810
=== removed file 'ubuntuone/oauthdesktop/config.py'
--- ubuntuone/oauthdesktop/config.py 2009-07-22 18:14:52 +0000
+++ ubuntuone/oauthdesktop/config.py 1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
1# ubuntuone.oauthdesktop.config - Configuration for OAuthDesktop
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"Find the config file for ubuntuone-oauth-config"
19import os, ConfigParser
20from xdg.BaseDirectory import load_first_config
21
22def get_config(use_tmpconfig=True):
23 """Return a ConfigParser object from a config file.
24 Config file is looked for in the source tree first and then in the
25 FreeDesktop BaseDirectory folders.
26 """
27 # if tmpconfig exists, then we are running out of the source tree
28 tmpconfig = os.path.realpath(os.path.join(
29 __file__, "../../../data/oauth_urls"
30 ))
31 if os.path.isfile(tmpconfig) and use_tmpconfig:
32 config_file = tmpconfig
33 else:
34 config_file = load_first_config('ubuntuone','oauth_urls')
35
36 cfp = ConfigParser.ConfigParser()
37 cfp.FILENAME = None
38 if config_file is not None:
39 try:
40 cfp.read(config_file)
41 cfp.FILENAME = config_file
42 except ConfigParser.Error:
43 cfp = ConfigParser.ConfigParser()
44 cfp.FILENAME = None
45 return cfp
46
47
480
=== removed file 'ubuntuone/oauthdesktop/key_acls.py'
--- ubuntuone/oauthdesktop/key_acls.py 2010-03-10 21:11:45 +0000
+++ ubuntuone/oauthdesktop/key_acls.py 1970-01-01 00:00:00 +0000
@@ -1,162 +0,0 @@
1# ubuntuone.oauthdesktop.key_acls - OAuth ACL handling for keyring
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""OAuth client authorisation code.
19
20This code finds all apps that have pre-registered themselves as wanting to
21access OAuth tokens from the Gnome keyring without the user having to approve
22that, and sets ACLs on relevant keys so they can do so.
23
24Apps pre-register themselves by dropping an ini file in
25/etc/xdg/ubuntuone/oauth_registration.d/ in which each section has keys
26realm, consumer_key, exe_path, application_name.
27"""
28
29import xdg.BaseDirectory, os, ConfigParser, gnomekeyring
30
31def get_privileged_config_folder(use_source_tree_folder=True):
32 """Find the XDG config folder to use which is not the user's personal
33 config (i.e., ~/.config) so that files in it are root-owned"""
34
35 # First, check for folder (if we're running from the source tree)
36 if use_source_tree_folder:
37 source_tree_folder = os.path.join(
38 os.path.split(__file__)[0],
39 "../../data/oauth_registration.d")
40 if os.path.isdir(source_tree_folder):
41 return os.path.join(source_tree_folder, "..")
42
43 # Otherwise, check for proper XDG folders
44 privileged_folders = [x for x in
45 xdg.BaseDirectory.load_config_paths('ubuntuone')
46 if not x.startswith(xdg.BaseDirectory.xdg_config_home)]
47 if privileged_folders:
48 return privileged_folders[0]
49 else:
50 return None
51
52def get_acl_preregistrations(use_source_tree_folder=True):
53 "Return a list of all config files in the pre-registration folder"
54 config_folder = get_privileged_config_folder(use_source_tree_folder)
55 if config_folder:
56 conf_dir = os.path.join(config_folder,
57 "oauth_registration.d")
58 if os.path.isdir(conf_dir):
59 return [os.path.join(conf_dir, x) for x in os.listdir(conf_dir)]
60 return []
61
62def get_item_ids_for_realm(realm, consumer_key):
63 "Find all keyring tokens for a specific realm/consumer_key"
64 if realm == "http://localhost":
65 # if realm (from the config file) is localhost, then the token
66 # will have ubuntuone-realm == http://localhost:SOMETHING
67 # so find all keys with this consumer_key, and pass on any
68 # where ubuntuone-realm begins with http://localhost:
69 try:
70 items = gnomekeyring.find_items_sync(
71 gnomekeyring.ITEM_GENERIC_SECRET,
72 {
73 'oauth-consumer-key': consumer_key})
74 except (gnomekeyring.NoMatchError,
75 gnomekeyring.DeniedError):
76 return []
77 items = [x.item_id for x in items if
78 x.attributes.get("ubuntuone-realm", "").startswith("http://localhost:")]
79 return items
80 else:
81 # realm was not localhost, so search for it explicitly
82 try:
83 items = gnomekeyring.find_items_sync(
84 gnomekeyring.ITEM_GENERIC_SECRET,
85 {'ubuntuone-realm': realm,
86 'oauth-consumer-key': consumer_key})
87 except (gnomekeyring.NoMatchError,
88 gnomekeyring.DeniedError):
89 return []
90 return [x.item_id for x in items]
91
92def set_single_acl(app_sets, specific_item_id=None):
93 """Allow a specified set of apps to access a matching keyring
94 token without prompts"""
95 for realm, consumer_key, exe_path, application_name in app_sets:
96 if specific_item_id is None:
97 items = get_item_ids_for_realm(realm, consumer_key)
98 else:
99 # item_id specified
100 items = [specific_item_id]
101
102 # set an ACL on the key so the calling app can read it without
103 # a prompt dialog
104 for item_id in items:
105 acl = gnomekeyring.item_get_acl_sync(None, item_id)
106 new_acls = False
107 real_exe_path = os.path.realpath(exe_path)
108 for acl_item in acl:
109 if acl_item.get_display_name() == application_name and \
110 acl_item.get_path_name() == real_exe_path:
111 # this ACL is already set
112 break
113 else:
114 appref = gnomekeyring.ApplicationRef()
115 ac = gnomekeyring.AccessControl(appref,
116 gnomekeyring.ACCESS_READ |
117 gnomekeyring.ACCESS_WRITE | gnomekeyring.ACCESS_REMOVE)
118 ac.set_display_name(application_name)
119 ac.set_path_name(real_exe_path)
120 acl.append(ac)
121 new_acls = True
122 if new_acls:
123 gnomekeyring.item_set_acl_sync(None, item_id, acl)
124
125def set_all_key_acls(item_id=None, use_source_tree_folder=True):
126 """For each file in the config folder, get the (realm, key) pair that
127 the program therein is interested in and register the program as able
128 to access those keys by setting an ACL on them."""
129 for config_file in get_acl_preregistrations(use_source_tree_folder):
130 cfp = ConfigParser.ConfigParser()
131 try:
132 cfp.read(config_file)
133 except ConfigParser.Error:
134 continue
135
136 app_sets = []
137
138 for section in cfp.sections():
139 try:
140 realm = cfp.get(section, "realm")
141 except ConfigParser.NoOptionError:
142 realm = None
143 try:
144 consumer_key = cfp.get(section, "consumer_key")
145 except ConfigParser.NoOptionError:
146 consumer_key = None
147 try:
148 exe_path = cfp.get(section, "exe_path")
149 except ConfigParser.NoOptionError:
150 exe_path = None
151 try:
152 application_name = cfp.get(section, "application_name")
153 except ConfigParser.NoOptionError:
154 application_name = None
155 if realm and consumer_key and exe_path and application_name:
156 app_sets.append((realm, consumer_key, exe_path,
157 application_name))
158
159 if app_sets:
160 set_single_acl(app_sets, specific_item_id=item_id)
161
162
1630
=== removed file 'ubuntuone/oauthdesktop/logger.py'
--- ubuntuone/oauthdesktop/logger.py 2009-10-16 19:56:57 +0000
+++ ubuntuone/oauthdesktop/logger.py 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1# ubuntuone.oauthdesktop.logger - logging miscellany
2#
3# Author: Stuart Langridge <stuart.langridge@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18"""Miscellaneous logging functions."""
19import os
20import logging
21import xdg.BaseDirectory
22
23from logging.handlers import RotatingFileHandler
24
25home = xdg.BaseDirectory.xdg_cache_home
26LOGFOLDER = os.path.join(home, 'ubuntuone', 'log')
27# create log folder if it doesn't exists
28if not os.path.exists(LOGFOLDER):
29 os.makedirs(LOGFOLDER)
30
31LOGFILENAME = os.path.join(LOGFOLDER, 'oauth-login.log')
32
33# Only log this level and above
34LOG_LEVEL = logging.INFO
35
36root_formatter = logging.Formatter(
37 fmt="%(asctime)s:%(msecs)s %(name)s %(message)s")
38root_handler = RotatingFileHandler(LOGFILENAME, maxBytes=1048576,
39 backupCount=1)
40root_handler.setLevel(LOG_LEVEL)
41root_handler.setFormatter(root_formatter)
42
43def setupLogging(log_domain):
44 """Create basic logger to set filename"""
45 logger = logging.getLogger(log_domain)
46 logger.propagate = False
47 logger.setLevel(LOG_LEVEL)
48 logger.addHandler(root_handler)
49 return logger
500
=== removed file 'ubuntuone/oauthdesktop/main.py'
--- ubuntuone/oauthdesktop/main.py 2010-03-30 21:11:52 +0000
+++ ubuntuone/oauthdesktop/main.py 1970-01-01 00:00:00 +0000
@@ -1,296 +0,0 @@
1#!/usr/bin/python
2
3# ubuntuone.oauthdesktop.main - main login handling interface
4#
5# Author: Stuart Langridge <stuart.langridge@canonical.com>
6#
7# Copyright 2009 Canonical Ltd.
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20"""OAuth login handler.
21
22A command-line utility which accepts requests for OAuth login over D-Bus,
23handles the OAuth process (including adding the OAuth access token to the
24gnome keyring), and then alerts the calling app (and others) with a D-Bus
25signal so they can retrieve the new token.
26"""
27
28import dbus.service, urlparse, time, gobject
29import pynotify
30
31from dbus.mainloop.glib import DBusGMainLoop
32
33from ubuntuone.oauthdesktop.config import get_config
34
35from ubuntuone.oauthdesktop.logger import setupLogging
36logger = setupLogging("UbuntuOne.OAuthDesktop.main")
37
38DBusGMainLoop(set_as_default=True)
39
40# Disable the invalid name warning, as we have a lot of DBus style names
41# pylint: disable-msg=C0103
42
43class NoDefaultConfigError(Exception):
44 """No default section in configuration file"""
45 pass
46
47class BadRealmError(Exception):
48 """Realm must be a URL"""
49 pass
50
51class LoginProcessor:
52 """Actually do the work of processing passed parameters"""
53 def __init__(self, dbus_object, use_libnotify=True):
54 """Initialize the login processor."""
55 logger.debug("Creating a LoginProcessor")
56 self.use_libnotify = use_libnotify
57 if self.use_libnotify and pynotify:
58 logger.debug("Hooking libnotify")
59 pynotify.init("UbuntuOne Login")
60 self.note1 = None
61 self.realm = None
62 self.consumer_key = None
63 self.dbus_object = dbus_object
64 logger.debug("Getting configuration")
65 self.config = get_config()
66
67 def login(self, realm, consumer_key, do_login=True):
68 """Initiate an OAuth login"""
69 logger.debug("Initiating OAuth login in LoginProcessor")
70 self.realm = str(realm) # because they are dbus.Strings, not str
71 self.consumer_key = str(consumer_key)
72
73 logger.debug("Obtaining OAuth urls")
74 (request_token_url, user_authorisation_url,
75 access_token_url, consumer_secret) = self.get_config_urls(realm)
76 logger.debug("OAuth URLs are: request='%s', userauth='%s', " +\
77 "access='%s', secret='%s'", request_token_url,
78 user_authorisation_url, access_token_url, consumer_secret)
79
80 from ubuntuone.oauthdesktop.auth import AuthorisationClient
81 client = AuthorisationClient(self.realm,
82 request_token_url,
83 user_authorisation_url,
84 access_token_url, self.consumer_key,
85 consumer_secret,
86 callback_parent=self.got_token,
87 callback_denied=self.got_denial,
88 callback_notoken=self.got_no_token,
89 callback_error=self.got_error,
90 do_login=do_login)
91
92 logger.debug("Calling auth.client.ensure_access_token in thread")
93 gobject.timeout_add_seconds(1, client.ensure_access_token)
94
95 def clear_token(self, realm, consumer_key):
96 """Remove the currently stored OAuth token from the keyring."""
97 self.realm = str(realm)
98 self.consumer_key = str(consumer_key)
99 (request_token_url, user_authorisation_url,
100 access_token_url, consumer_secret) = self.get_config_urls(self.realm)
101 from ubuntuone.oauthdesktop.auth import AuthorisationClient
102 client = AuthorisationClient(self.realm,
103 request_token_url,
104 user_authorisation_url,
105 access_token_url,
106 self.consumer_key, consumer_secret,
107 callback_parent=self.got_token,
108 callback_denied=self.got_denial,
109 callback_notoken=self.got_no_token,
110 callback_error=self.got_error)
111 gobject.timeout_add_seconds(1, client.clear_token)
112
113 def error_handler(self, failure):
114 """Deal with errors returned from auth process"""
115 logger.debug("Error returned from auth process")
116 self.dbus_object.currently_authing = False # not block future requests
117
118 def get_config_urls(self, realm):
119 """Look up the URLs to use in the config file"""
120 logger.debug("Fetching config URLs for realm='%s'", realm)
121 if self.config.has_section(realm):
122 logger.debug("Realm '%s' is in config", realm)
123 request_token_url = self.__get_url(realm, "request_token_url")
124 user_authorisation_url = self.__get_url(realm,
125 "user_authorisation_url")
126 access_token_url = self.__get_url(realm, "access_token_url")
127 consumer_secret = self.__get_option(realm, "consumer_secret")
128 elif realm.startswith("http://localhost") and \
129 self.config.has_section("http://localhost"):
130 logger.debug("Realm is localhost and is in config")
131 request_token_url = self.__get_url("http://localhost",
132 "request_token_url", realm)
133 user_authorisation_url = self.__get_url("http://localhost",
134 "user_authorisation_url", realm)
135 access_token_url = self.__get_url("http://localhost",
136 "access_token_url", realm)
137 consumer_secret = self.__get_option("http://localhost",
138 "consumer_secret")
139 elif self.is_valid_url(realm):
140 logger.debug("Realm '%s' is not in config", realm)
141 request_token_url = self.__get_url("default",
142 "request_token_url", realm)
143 user_authorisation_url = self.__get_url("default",
144 "user_authorisation_url", realm)
145 access_token_url = self.__get_url("default",
146 "access_token_url", realm)
147 consumer_secret = self.__get_option(realm, "consumer_secret")
148 else:
149 logger.debug("Realm '%s' is a bad realm", realm)
150 raise BadRealmError
151 return (request_token_url, user_authorisation_url,
152 access_token_url, consumer_secret)
153
154 def is_valid_url(self, url):
155 """Simple check for URL validity"""
156 # pylint: disable-msg=W0612
157 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
158 if scheme and netloc:
159 return True
160 else:
161 return False
162
163 def got_token(self, access_token):
164 """Callback function when access token has been retrieved"""
165 logger.debug("Token retrieved, calling NewCredentials function")
166 self.dbus_object.NewCredentials(self.realm, self.consumer_key)
167
168 def got_denial(self):
169 """Callback function when request token has been denied"""
170 self.dbus_object.AuthorizationDenied()
171
172 def got_no_token(self):
173 """Callback function when access token is not in keyring."""
174 self.dbus_object.NoCredentials()
175
176 def got_error(self, message):
177 """Callback function to emit an error message over DBus."""
178 self.dbus_object.OAuthError(message)
179
180 def __get_url(self, realm, option, actual_realm=None):
181 """Construct a full URL from realm and a URLpath for that realm in
182 the config file."""
183 if actual_realm:
184 realm_to_use = actual_realm
185 else:
186 realm_to_use = realm
187 urlstub = self.__get_option(realm, option)
188 return urlparse.urljoin(realm_to_use, urlstub)
189
190 def __get_option(self, realm, option):
191 """Return a specific option for that realm in
192 the config file. If the realm does not exist in the config file,
193 fall back to the [default] section."""
194 if self.config.has_section(realm) and \
195 self.config.has_option(realm, option):
196 urlstub = self.config.get(realm, option)
197 return urlstub
198
199 # either the realm exists and this url does not, or
200 # the realm doesn't exist; either way, fall back to [default] section
201 urlstub = self.config.get("default", option, None)
202 if urlstub is not None:
203 return urlstub
204
205 # this url does not exist in default section either
206 # this shouldn't happen
207 raise NoDefaultConfigError("No default configuration for %s" % option)
208
209
210class Login(dbus.service.Object):
211 """Object which listens for D-Bus OAuth requests"""
212 def __init__(self, bus_name):
213 """Initiate the Login object."""
214 dbus.service.Object.__init__(self, object_path="/", bus_name=bus_name)
215 self.processor = LoginProcessor(self)
216 self.currently_authing = False
217 logger.debug("Login D-Bus service starting up")
218
219 @dbus.service.method(dbus_interface='com.ubuntuone.Authentication',
220 in_signature='ss', out_signature='')
221 def login(self, realm, consumer_key):
222 """D-Bus method, exported over the bus, to initiate an OAuth login"""
223 logger.debug("login() D-Bus message received with realm='%s', " +
224 "consumer_key='%s'", realm, consumer_key)
225 if self.currently_authing:
226 logger.debug("Currently in the middle of OAuth: rejecting this")
227 return
228 self.currently_authing = True
229 self.processor.login(realm, consumer_key)
230
231 @dbus.service.method(dbus_interface='com.ubuntuone.Authentication',
232 in_signature='ssb', out_signature='')
233 def maybe_login(self, realm, consumer_key, do_login):
234 """
235 D-Bus method, exported over the bus, to maybe initiate an OAuth login
236 """
237 logger.debug("maybe_login() D-Bus message received with realm='%s', " +
238 "consumer_key='%s'", realm, consumer_key)
239 if self.currently_authing:
240 logger.debug("Currently in the middle of OAuth: rejecting this")
241 return
242 self.currently_authing = True
243 self.processor.login(realm, consumer_key, do_login)
244
245 @dbus.service.method(dbus_interface='com.ubuntuone.Authentication',
246 in_signature='ss', out_signature='')
247 def clear_token(self, realm, consumer_key):
248 """
249 D-Bus method, exported over the bus, to clear the existing token.
250 """
251 self.processor.clear_token(realm, consumer_key)
252
253 @dbus.service.signal(dbus_interface='com.ubuntuone.Authentication',
254 signature='ss')
255 def NewCredentials(self, realm, consumer_key):
256 """Fire D-Bus signal when the user accepts authorization."""
257 logger.debug("Firing the NewCredentials signal")
258 self.currently_authing = False
259 return (self.processor.realm, self.processor.consumer_key)
260
261 @dbus.service.signal(dbus_interface='com.ubuntuone.Authentication')
262 def AuthorizationDenied(self):
263 """Fire the signal when the user denies authorization."""
264 self.currently_authing = False
265
266 @dbus.service.signal(dbus_interface='com.ubuntuone.Authentication')
267 def NoCredentials(self):
268 """Fired when the user does not have a token in the keyring."""
269 self.currently_authing = False
270
271 @dbus.service.signal(dbus_interface='com.ubuntuone.Authentication',
272 signature='s')
273 def OAuthError(self, message):
274 """Fire the signal when an error needs to be propagated to the user."""
275 self.currently_authing = False
276 return message
277
278def main():
279 """Start everything"""
280 logger.debug("Starting up at %s", time.asctime())
281 logger.debug("Installing the Twisted glib2reactor")
282 from twisted.internet import glib2reactor # for non-GUI apps
283 glib2reactor.install()
284 from twisted.internet import reactor
285
286 logger.debug("Creating the D-Bus service")
287 Login(dbus.service.BusName("com.ubuntuone.Authentication",
288 bus=dbus.SessionBus()))
289 # cleverness here to say:
290 # am I already running (bound to this d-bus name)?
291 # if so, send a signal to the already running instance
292 # this means that this app can be started from an x-ubutnuone: URL
293 # to kick off the signin process
294 logger.debug("Starting the reactor mainloop")
295 reactor.run()
296
2970
=== modified file 'ubuntuone/syncdaemon/dbus_interface.py'
--- ubuntuone/syncdaemon/dbus_interface.py 2010-06-02 18:00:29 +0000
+++ ubuntuone/syncdaemon/dbus_interface.py 2010-06-16 20:35:34 +0000
@@ -33,7 +33,7 @@
33from ubuntuone.syncdaemon.interfaces import IMarker33from ubuntuone.syncdaemon.interfaces import IMarker
34from ubuntuone.syncdaemon import config34from ubuntuone.syncdaemon import config
35from ubuntuone.syncdaemon.volume_manager import Share, UDF, VolumeDoesNotExist35from ubuntuone.syncdaemon.volume_manager import Share, UDF, VolumeDoesNotExist
3636from ubuntu_sso import DBUS_IFACE_AUTH_NAME, DBUS_PATH_AUTH
3737
38# Disable the "Invalid Name" check here, as we have lots of DBus style names38# Disable the "Invalid Name" check here, as we have lots of DBus style names
39# pylint: disable-msg=C010339# pylint: disable-msg=C0103
@@ -58,10 +58,6 @@
58NM_STATE_EVENTS = {NM_STATE_CONNECTED: 'SYS_NET_CONNECTED',58NM_STATE_EVENTS = {NM_STATE_CONNECTED: 'SYS_NET_CONNECTED',
59 NM_STATE_DISCONNECTED: 'SYS_NET_DISCONNECTED'}59 NM_STATE_DISCONNECTED: 'SYS_NET_DISCONNECTED'}
6060
61# OAuthDesktop constants
62DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
63DBUS_PATH_AUTH = "/"
64
65logger = logging.getLogger("ubuntuone.SyncDaemon.DBus")61logger = logging.getLogger("ubuntuone.SyncDaemon.DBus")
6662
67def get_classname(thing):63def get_classname(thing):

Subscribers

People subscribed via source and target branches