1
# -*- coding: utf-8 -*-
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>
7
# Copyright 2011 Canonical Ltd.
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.
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.
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."""
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
34
logger = setup_logging("ubuntu_sso.main")
35
U1_PING_URL = "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"
38
class SSOLoginProcessor(Account):
39
"""Login and register users using the Ubuntu Single Sign On service.
41
Alias classname to maintain backwards compatibility. DO NOT USE, use
42
ubuntu_sso.account.Account instead.
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)
52
def except_to_errdict(e):
53
"""Turn an exception into a dictionary to return thru DBus."""
55
"errtype": e.__class__.__name__,
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]
67
class SSOLoginRoot(object):
68
"""Login thru the Single Sign On service."""
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)
77
def generate_captcha(self, app_name, filename, thread_execute, result_cb,
79
"""Call the matching method in the processor."""
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)
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."""
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)
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."""
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>.',
102
credentials = self.processor.login(email, password, token_name)
103
logger.debug('login returned not None credentials? %r.',
104
credentials is not None)
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)
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: \
117
except_to_errdict(failure.value)))
119
not_validated_cb(app_name, email)
120
thread_execute(f, app_name, success_cb, error_cb)
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."""
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)
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)
141
thread_execute(f, app_name, success_cb, error_cb)
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."""
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)
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."""
155
"""Inner function that will be run in a thread."""
156
return self.processor.set_new_password(email, token,
158
thread_execute(f, app_name, result_cb, error_cb)
161
class SSOCredentialsRoot(object):
162
"""Object that gets credentials, and login/registers if needed."""
165
self.ping_url = os.environ.get('USSOC_PING_URL', U1_PING_URL)
167
def find_credentials(self, app_name, callback=NO_OP, errback=NO_OP):
168
"""Get the credentials from the keyring or {} if not there."""
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 == {})
176
d = Credentials(app_name=app_name).find_credentials()
177
# pylint: disable=E1101
178
d.addCallback(log_result)
179
d.addCallbacks(callback, errback)
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.
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
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.
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,
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
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.
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,
221
def clear_token(self, app_name, callback=NO_OP, errback=NO_OP):
222
"""Clear the token for an application from the keyring.
224
'app_name' is the name of the application.
226
d = Credentials(app_name=app_name).clear_credentials()
227
# pylint: disable=E1101
228
d.addCallbacks(lambda _: callback(), errback)
231
class CredentialsManagementRoot(object):
232
"""Object that manages credentials.
234
Every exposed method in this class requires one mandatory argument:
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.
239
And accepts another parameter named 'args', which is a dictionary that
240
can contain the following:
242
- 'help_text': an explanatory text for the end-users, will be
243
shown below the header. This is an optional free text field.
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.
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.
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.
257
def __init__(self, found_cb, error_cb, denied_cb, *args, **kwargs):
258
"""Create a new instance.
260
- 'found_cb' is a callback that will be executed when the credentials
263
- 'error_cb' is a callback that will be executed when there was an
264
error getting the credentials.
266
- 'denied_cb' is a callback that will be executed when the user denied
267
the use of the crendetials.
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
275
valid_keys = (HELP_TEXT_KEY, PING_URL_KEY, TC_URL_KEY,
276
UI_CLASS_KEY, UI_MODULE_KEY, WINDOW_ID_KEY)
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
287
def find_credentials(self, app_name, args, success_cb, error_cb):
288
"""Look for the credentials for an application.
290
- 'app_name': the name of the application which credentials are
293
- 'args' is a dictionary, currently not used.
295
- 'success_cb' is a callback that will be execute if the operation was
298
- 'error_cb' is a callback that will be executed if the operation had
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)
309
def clear_credentials(self, app_name, args, success_cb, error_cb):
310
"""Clear the credentials for an application.
312
- 'app_name': the name of the application which credentials are
315
- 'args' is a dictionary, currently not used.
317
- 'success_cb' is a callback that will be execute if the operation was
320
- 'error_cb' is a callback that will be executed if the operation had
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)
331
def store_credentials(self, app_name, args, success_cb, error_cb):
332
"""Store the token for an application.
334
- 'app_name': the name of the application which credentials are
337
- 'args' is the dictionary holding the credentials. Needs to provide
338
the following mandatory keys: 'token', 'token_key', 'consumer_key',
341
- 'success_cb' is a callback that will be execute if the operation was
344
- 'error_cb' is a callback that will be executed if the operation had
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)
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))
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))
364
# pylint: disable=C0103
366
SSOCredentials = None
367
CredentialsManagement = None
369
if sys.platform == 'win32':
372
from ubuntu_sso.main import linux
373
SSOLogin = linux.SSOLogin
374
SSOCredentials = linux.SSOCredentials
375
CredentialsManagement = linux.CredentialsManagement