~didrocks/ubuntuone-client/dont-suffer-zg-crash

« back to all changes in this revision

Viewing changes to ubuntuone/oauthdesktop/main.py

  • Committer: Bazaar Package Importer
  • Author(s): Rodrigo Moya
  • Date: 2010-06-23 23:08:15 UTC
  • mto: This revision was merged to the branch mainline in revision 34.
  • Revision ID: james.westby@ubuntu.com-20100623230815-4m3ugh10u9x9xzw5
Tags: upstream-1.3.2
ImportĀ upstreamĀ versionĀ 1.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 
22
 
A command-line utility which accepts requests for OAuth login over D-Bus,
23
 
handles the OAuth process (including adding the OAuth access token to the
24
 
gnome keyring), and then alerts the calling app (and others) with a D-Bus
25
 
signal so they can retrieve the new token.
26
 
"""
27
 
 
28
 
import dbus.service, urlparse, time, gobject
29
 
import pynotify
30
 
 
31
 
from dbus.mainloop.glib import DBusGMainLoop
32
 
 
33
 
from ubuntuone.oauthdesktop.config import get_config
34
 
 
35
 
from ubuntuone.oauthdesktop.logger import setupLogging
36
 
logger = setupLogging("UbuntuOne.OAuthDesktop.main")
37
 
 
38
 
DBusGMainLoop(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
 
 
43
 
class NoDefaultConfigError(Exception):
44
 
    """No default section in configuration file"""
45
 
    pass
46
 
 
47
 
class BadRealmError(Exception):
48
 
    """Realm must be a URL"""
49
 
    pass
50
 
 
51
 
class 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
 
 
210
 
class 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
 
 
278
 
def 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