~mvo/ubuntu-sso-client/strawman-lp711413

« back to all changes in this revision

Viewing changes to ubuntu_sso/main/__init__.py

  • Committer: Tarmac
  • Author(s): Manuel de la Pena
  • Date: 2011-03-17 09:01:50 UTC
  • mfrom: (675.4.12 main_1)
  • Revision ID: tarmac-20110317090150-wl4sniuy86qd9ci2
First step of implementing the code in main on windows.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Author: Natalia Bidart <natalia.bidart@canonical.com>
 
4
# Author: Alejandro J. Cura <alecu@canonical.com>
 
5
# Author: Manuel de la Pena <manuel@canonical.com>
 
6
#
 
7
# Copyright 2011 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
"""Main object implementations."""
 
21
 
 
22
import os
 
23
import sys
 
24
import warnings
 
25
 
 
26
from ubuntu_sso import NO_OP
 
27
from ubuntu_sso.account import Account
 
28
from ubuntu_sso.credentials import (Credentials, HELP_TEXT_KEY, PING_URL_KEY,
 
29
    TC_URL_KEY, UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY,
 
30
    SUCCESS_CB_KEY, ERROR_CB_KEY, DENIAL_CB_KEY)
 
31
from ubuntu_sso.keyring import get_token_name, U1_APP_NAME, Keyring
 
32
from ubuntu_sso.logger import setup_logging
 
33
 
 
34
logger = setup_logging("ubuntu_sso.main")
 
35
U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
 
36
 
 
37
 
 
38
class SSOLoginProcessor(Account):
 
39
    """Login and register users using the Ubuntu Single Sign On service.
 
40
 
 
41
    Alias classname to maintain backwards compatibility. DO NOT USE, use
 
42
    ubuntu_sso.account.Account instead.
 
43
    """
 
44
 
 
45
    def __init__(self, sso_service_class=None):
 
46
        """Create a new SSO Account manager."""
 
47
        msg = 'Use ubuntu_sso.account.Account instead.'
 
48
        warnings.warn(msg, DeprecationWarning)
 
49
        super(SSOLoginProcessor, self).__init__(sso_service_class)
 
50
 
 
51
 
 
52
def except_to_errdict(e):
 
53
    """Turn an exception into a dictionary to return thru DBus."""
 
54
    result = {
 
55
        "errtype": e.__class__.__name__,
 
56
    }
 
57
    if len(e.args) == 0:
 
58
        result["message"] = e.__class__.__doc__
 
59
    elif isinstance(e.args[0], dict):
 
60
        result.update(e.args[0])
 
61
    elif isinstance(e.args[0], basestring):
 
62
        result["message"] = e.args[0]
 
63
 
 
64
    return result
 
65
 
 
66
 
 
67
class SSOLoginRoot(object):
 
68
    """Login thru the Single Sign On service."""
 
69
 
 
70
    def __init__(self, sso_login_processor_class=Account,
 
71
                 sso_service_class=None):
 
72
        """Initiate the Login object."""
 
73
        self.sso_login_processor_class = sso_login_processor_class
 
74
        self.processor = self.sso_login_processor_class(
 
75
                                    sso_service_class=sso_service_class)
 
76
 
 
77
    def generate_captcha(self, app_name, filename, thread_execute, result_cb,
 
78
                         error_cb):
 
79
        """Call the matching method in the processor."""
 
80
        def f():
 
81
            """Inner function that will be run in a thread."""
 
82
            return self.processor.generate_captcha(filename)
 
83
        thread_execute(f, app_name, result_cb, error_cb)
 
84
 
 
85
    def register_user(self, app_name, email, password, captcha_id,
 
86
                      captcha_solution, thread_execute, result_cb, error_cb):
 
87
        """Call the matching method in the processor."""
 
88
        def f():
 
89
            """Inner function that will be run in a thread."""
 
90
            return self.processor.register_user(email, password,
 
91
                                                captcha_id, captcha_solution)
 
92
        thread_execute(f, app_name, result_cb, error_cb)
 
93
 
 
94
    def login(self, app_name, email, password, thread_execute, result_cb,
 
95
              error_cb, not_validated_cb):
 
96
        """Call the matching method in the processor."""
 
97
        def f():
 
98
            """Inner function that will be run in a thread."""
 
99
            token_name = get_token_name(app_name)
 
100
            logger.debug('login: token_name %r, email %r, password <hidden>.',
 
101
                         token_name, email)
 
102
            credentials = self.processor.login(email, password, token_name)
 
103
            logger.debug('login returned not None credentials? %r.',
 
104
                         credentials is not None)
 
105
            return credentials
 
106
 
 
107
        def success_cb(app_name, credentials):
 
108
            """Login finished successfull."""
 
109
            is_validated = self.processor.is_validated(credentials)
 
110
            logger.debug('user is validated? %r.', is_validated)
 
111
            if is_validated:
 
112
                # pylint: disable=E1101
 
113
                d = Keyring().set_credentials(app_name, credentials)
 
114
                d.addCallback(lambda _: result_cb(app_name, email))
 
115
                d.addErrback(lambda failure: \
 
116
                             error_cb(app_name,
 
117
                                      except_to_errdict(failure.value)))
 
118
            else:
 
119
                not_validated_cb(app_name, email)
 
120
        thread_execute(f, app_name, success_cb, error_cb)
 
121
 
 
122
    def validate_email(self, app_name, email, password, email_token,
 
123
                       thread_execute, result_cb, error_cb):
 
124
        """Call the matching method in the processor."""
 
125
 
 
126
        def f():
 
127
            """Inner function that will be run in a thread."""
 
128
            token_name = get_token_name(app_name)
 
129
            credentials = self.processor.validate_email(email, password,
 
130
                                                      email_token, token_name)
 
131
            return credentials
 
132
 
 
133
        def success_cb(app_name, credentials):
 
134
            """Validation finished successfully."""
 
135
            # pylint: disable=E1101
 
136
            d = Keyring().set_credentials(app_name, credentials)
 
137
            d.addCallback(lambda _: result_cb(app_name, email))
 
138
            failure_cb = lambda f: error_cb(app_name, f.value)
 
139
            d.addErrback(failure_cb)
 
140
 
 
141
        thread_execute(f, app_name, success_cb, error_cb)
 
142
 
 
143
    def request_password_reset_token(self, app_name, email, thread_execute,
 
144
                                     result_cb, error_cb):
 
145
        """Call the matching method in the processor."""
 
146
        def f():
 
147
            """Inner function that will be run in a thread."""
 
148
            return self.processor.request_password_reset_token(email)
 
149
        thread_execute(f, app_name, result_cb, error_cb)
 
150
 
 
151
    def set_new_password(self, app_name, email, token, new_password,
 
152
                         thread_execute, result_cb, error_cb):
 
153
        """Call the matching method in the processor."""
 
154
        def f():
 
155
            """Inner function that will be run in a thread."""
 
156
            return self.processor.set_new_password(email, token,
 
157
                                                   new_password)
 
158
        thread_execute(f, app_name, result_cb, error_cb)
 
159
 
 
160
 
 
161
class SSOCredentialsRoot(object):
 
162
    """Object that gets credentials, and login/registers if needed."""
 
163
 
 
164
    def __init__(self):
 
165
        self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)
 
166
 
 
167
    def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
 
168
        """Get the credentials from the keyring or {} if not there."""
 
169
 
 
170
        def log_result(result):
 
171
            """Log the result and continue."""
 
172
            logger.info('find_credentials: app_name "%s", result is {}? %s',
 
173
                        app_name, result == {})
 
174
            return result
 
175
 
 
176
        d = Credentials(app_name=app_name).find_credentials()
 
177
        # pylint: disable=E1101
 
178
        d.addCallback(log_result)
 
179
        d.addCallbacks(callback, errback)
 
180
 
 
181
    def login_or_register_to_get_credentials(self, app_name,
 
182
                                             terms_and_conditions_url,
 
183
                                             help_text, window_id,
 
184
                                             success_cb, error_cb, denial_cb):
 
185
        """Get credentials if found else prompt GUI to login or register.
 
186
 
 
187
        'app_name' will be displayed in the GUI.
 
188
        'terms_and_conditions_url' will be the URL pointing to T&C.
 
189
        'help_text' is an explanatory text for the end-users, will be shown
 
190
         below the headers.
 
191
        'window_id' is the id of the window which will be set as a parent of
 
192
         the GUI. If 0, no parent will be set.
 
193
 
 
194
        """
 
195
        ping_url = self.ping_url if app_name == U1_APP_NAME else None
 
196
        obj = Credentials(app_name=app_name, ping_url=ping_url,
 
197
                          tc_url=terms_and_conditions_url,
 
198
                          help_text=help_text, window_id=window_id,
 
199
                          success_cb=success_cb, error_cb=error_cb,
 
200
                          denial_cb=denial_cb)
 
201
        obj.register()
 
202
 
 
203
    def login_to_get_credentials(self, app_name, help_text, window_id,
 
204
                                success_cb, error_cb, denial_cb):
 
205
        """Get credentials if found else prompt GUI just to login
 
206
 
 
207
        'app_name' will be displayed in the GUI.
 
208
        'help_text' is an explanatory text for the end-users, will be shown
 
209
         before the login fields.
 
210
        'window_id' is the id of the window which will be set as a parent of
 
211
         the GUI. If 0, no parent will be set.
 
212
 
 
213
        """
 
214
        ping_url = self.ping_url if app_name == U1_APP_NAME else None
 
215
        obj = Credentials(app_name=app_name, ping_url=ping_url, tc_url=None,
 
216
                          help_text=help_text, window_id=window_id,
 
217
                          success_cb=success_cb, error_cb=error_cb,
 
218
                          denial_cb=denial_cb)
 
219
        obj.login()
 
220
 
 
221
    def clear_token(self, app_name, callback=NO_OP, errback=NO_OP):
 
222
        """Clear the token for an application from the keyring.
 
223
 
 
224
        'app_name' is the name of the application.
 
225
        """
 
226
        d = Credentials(app_name=app_name).clear_credentials()
 
227
        # pylint: disable=E1101
 
228
        d.addCallbacks(lambda _: callback(), errback)
 
229
 
 
230
 
 
231
class CredentialsManagementRoot(object):
 
232
    """Object that manages credentials.
 
233
 
 
234
    Every exposed method in this class requires one mandatory argument:
 
235
 
 
236
        - 'app_name': the name of the application. Will be displayed in the
 
237
        GUI header, plus it will be used to find/build/clear tokens.
 
238
 
 
239
    And accepts another parameter named 'args', which is a dictionary that
 
240
    can contain the following:
 
241
 
 
242
        - 'help_text': an explanatory text for the end-users, will be
 
243
        shown below the header. This is an optional free text field.
 
244
 
 
245
        - 'ping_url': the url to open after successful token retrieval. If
 
246
        defined, the email will be attached to the url and will be pinged
 
247
        with a OAuth-signed request.
 
248
 
 
249
        - 'tc_url': the link to the Terms and Conditions page. If defined,
 
250
        the checkbox to agree to the terms will link to it.
 
251
 
 
252
        - 'window_id': the id of the window which will be set as a parent
 
253
        of the GUI. If not defined, no parent will be set.
 
254
 
 
255
    """
 
256
 
 
257
    def __init__(self, found_cb, error_cb, denied_cb, *args, **kwargs):
 
258
        """Create a new instance.
 
259
 
 
260
        - 'found_cb' is a callback that will be executed when the credentials
 
261
        were found.
 
262
 
 
263
        - 'error_cb' is a callback that will be executed when there was an
 
264
        error getting the credentials.
 
265
 
 
266
        - 'denied_cb' is a callback that will be executed when the user denied
 
267
        the use of the crendetials.
 
268
 
 
269
        """
 
270
        super(CredentialsManagementRoot, self).__init__(*args, **kwargs)
 
271
        self.found_cb = found_cb
 
272
        self.error_cb = error_cb
 
273
        self.denied_cb = denied_cb
 
274
 
 
275
    valid_keys = (HELP_TEXT_KEY, PING_URL_KEY, TC_URL_KEY,
 
276
                  UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY)
 
277
 
 
278
    def _parse_args(self, args):
 
279
        """Retrieve values from the generic param 'args'."""
 
280
        result = dict(i for i in args.iteritems() if i[0] in self.valid_keys)
 
281
        result[WINDOW_ID_KEY] = int(args.get(WINDOW_ID_KEY, 0))
 
282
        result[SUCCESS_CB_KEY] = self.found_cb
 
283
        result[ERROR_CB_KEY] = self.error_cb
 
284
        result[DENIAL_CB_KEY] = self.denied_cb
 
285
        return result
 
286
 
 
287
    def find_credentials(self, app_name, args, success_cb, error_cb):
 
288
        """Look for the credentials for an application.
 
289
 
 
290
        - 'app_name': the name of the application which credentials are
 
291
        going to be removed.
 
292
 
 
293
        - 'args' is a dictionary, currently not used.
 
294
 
 
295
        - 'success_cb' is a callback that will be execute if the operation was
 
296
        a success.
 
297
 
 
298
        - 'error_cb' is a callback that will be executed if the operation had
 
299
        an error.
 
300
 
 
301
        """
 
302
 
 
303
        obj = Credentials(app_name)
 
304
        d = obj.find_credentials()
 
305
        # pylint: disable=E1101
 
306
        d.addCallback(success_cb)
 
307
        d.addErrback(error_cb, app_name)
 
308
 
 
309
    def clear_credentials(self, app_name, args, success_cb, error_cb):
 
310
        """Clear the credentials for an application.
 
311
 
 
312
        - 'app_name': the name of the application which credentials are
 
313
        going to be removed.
 
314
 
 
315
        - 'args' is a dictionary, currently not used.
 
316
 
 
317
        - 'success_cb' is a callback that will be execute if the operation was
 
318
        a success.
 
319
 
 
320
        - 'error_cb' is a callback that will be executed if the operation had
 
321
        an error.
 
322
 
 
323
        """
 
324
 
 
325
        obj = Credentials(app_name)
 
326
        d = obj.clear_credentials()
 
327
        # pylint: disable=E1101
 
328
        d.addCallback(success_cb)
 
329
        d.addErrback(error_cb, app_name)
 
330
 
 
331
    def store_credentials(self, app_name, args, success_cb, error_cb):
 
332
        """Store the token for an application.
 
333
 
 
334
        - 'app_name': the name of the application which credentials are
 
335
        going to be stored.
 
336
 
 
337
        - 'args' is the dictionary holding the credentials. Needs to provide
 
338
        the following mandatory keys: 'token', 'token_key', 'consumer_key',
 
339
        'consumer_secret'.
 
340
 
 
341
        - 'success_cb' is a callback that will be execute if the operation was
 
342
        a success.
 
343
 
 
344
        - 'error_cb' is a callback that will be executed if the operation had
 
345
        an error.
 
346
        """
 
347
 
 
348
        obj = Credentials(app_name)
 
349
        d = obj.store_credentials(args)
 
350
        # pylint: disable=E1101
 
351
        d.addCallback(success_cb)
 
352
        d.addErrback(error_cb, app_name)
 
353
 
 
354
    def register(self, app_name, args):
 
355
        """Get credentials if found else prompt GUI to register."""
 
356
        obj = Credentials(app_name, **self._parse_args(args))
 
357
        obj.register()
 
358
 
 
359
    def login(self, app_name, args):
 
360
        """Get credentials if found else prompt GUI to login."""
 
361
        obj = Credentials(app_name, **self._parse_args(args))
 
362
        obj.login()
 
363
 
 
364
# pylint: disable=C0103
 
365
SSOLogin = None
 
366
SSOCredentials = None
 
367
CredentialsManagement = None
 
368
 
 
369
if sys.platform == 'win32':
 
370
    pass
 
371
else:
 
372
    from ubuntu_sso.main import linux
 
373
    SSOLogin = linux.SSOLogin
 
374
    SSOCredentials = linux.SSOCredentials
 
375
    CredentialsManagement = linux.CredentialsManagement