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

« back to all changes in this revision

Viewing changes to ubuntu_sso/account.py

  • Committer: Natalia B. Bidart
  • Date: 2011-12-20 16:29:34 UTC
  • Revision ID: natalia.bidart@canonical.com-20111220162934-2s5xou06v3usxyr6
Tags: ubuntu-sso-client-2_99_0
- Release v2.99.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
# Author: Natalia Bidart <natalia.bidart@canonical.com>
4
4
# Author: Alejandro J. Cura <alecu@canonical.com>
5
5
#
6
 
# Copyright 2010-2012 Canonical Ltd.
 
6
# Copyright 2010 Canonical Ltd.
7
7
#
8
8
# This program is free software: you can redistribute it and/or modify it
9
9
# under the terms of the GNU General Public License version 3, as published
16
16
#
17
17
# You should have received a copy of the GNU General Public License along
18
18
# with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
# In addition, as a special exception, the copyright holders give
21
 
# permission to link the code of portions of this program with the
22
 
# OpenSSL library under certain conditions as described in each
23
 
# individual source file, and distribute linked combinations
24
 
# including the two.
25
 
# You must obey the GNU General Public License in all respects
26
 
# for all of the code used other than OpenSSL.  If you modify
27
 
# file(s) with this exception, you may extend this exception to your
28
 
# version of the file(s), but you are not obligated to do so.  If you
29
 
# do not wish to do so, delete this exception statement from your
30
 
# version.  If you delete this exception statement from all source
31
 
# files in the program, then also delete it here.
32
 
"""Single Sign On account management.
33
 
 
34
 
All the methods in Account expect unicode as parameters.
35
 
 
36
 
"""
 
19
"""Single Sign On account management."""
37
20
 
38
21
import os
39
22
import re
 
23
import urllib2
40
24
 
 
25
# Unable to import 'lazr.restfulclient.*'
 
26
# pylint: disable=F0401
 
27
from lazr.restfulclient.authorize import BasicHttpAuthorizer
41
28
from lazr.restfulclient.authorize.oauth import OAuthAuthorizer
 
29
from lazr.restfulclient.errors import HTTPError
 
30
from lazr.restfulclient.resource import ServiceRoot
 
31
# pylint: enable=F0401
42
32
from oauth import oauth
43
 
from twisted.internet import defer
44
33
 
45
34
from ubuntu_sso.logger import setup_logging
46
 
from ubuntu_sso.utils import webclient
47
 
from ubuntu_sso.utils.webclient import restful
48
 
from ubuntu_sso.utils.webclient.common import WebClientError
 
35
from ubuntu_sso.utils import timestamp_checker
49
36
 
50
37
 
51
38
logger = setup_logging("ubuntu_sso.account")
52
 
SERVICE_URL = u"https://login.ubuntu.com/api/1.0/"
 
39
SERVICE_URL = "https://login.ubuntu.com/api/1.0"
53
40
SSO_STATUS_OK = 'ok'
54
41
SSO_STATUS_ERROR = 'error'
55
42
 
109
96
class Account(object):
110
97
    """Login and register users using the Ubuntu Single Sign On service."""
111
98
 
112
 
    def __init__(self, service_url=None):
 
99
    def __init__(self, sso_service_class=None):
113
100
        """Create a new SSO Account manager."""
114
 
        if service_url is not None:
115
 
            self.service_url = service_url
 
101
        if sso_service_class is None:
 
102
            self.sso_service_class = ServiceRoot
116
103
        else:
117
 
            self.service_url = os.environ.get('USSOC_SERVICE_URL', SERVICE_URL)
118
 
        logger.info('Creating a new SSO access layer for service url %r',
 
104
            self.sso_service_class = sso_service_class
 
105
 
 
106
        self.service_url = os.environ.get('USSOC_SERVICE_URL', SERVICE_URL)
 
107
 
 
108
        logger.info('Created a new SSO access layer for service url %r',
119
109
                     self.service_url)
120
 
        assert self.service_url.endswith("/")
121
110
 
122
111
    def _valid_email(self, email):
123
112
        """Validate the given email."""
141
130
                result[key] = "\n".join(val)
142
131
        return result
143
132
 
144
 
    @defer.inlineCallbacks
145
133
    def generate_captcha(self, filename):
146
134
        """Generate a captcha using the SSO service."""
147
135
        logger.debug('generate_captcha: requesting captcha, filename: %r',
148
136
                     filename)
149
 
        restful_client = restful.RestfulClient(self.service_url)
150
 
        try:
151
 
            captcha = yield restful_client.restcall(u"captchas.new")
152
 
        finally:
153
 
            restful_client.shutdown()
 
137
        sso_service = self.sso_service_class(None, self.service_url)
 
138
        captcha = sso_service.captchas.new()
154
139
 
155
140
        # download captcha and save to 'filename'
156
141
        logger.debug('generate_captcha: server answered: %r', captcha)
157
 
        wc = webclient.webclient_factory()
158
142
        try:
159
 
            response = yield wc.request(captcha['image_url'])
 
143
            res = urllib2.urlopen(captcha['image_url'])
160
144
            with open(filename, 'wb') as f:
161
 
                f.write(response.content)
 
145
                f.write(res.read())
162
146
        except:
163
147
            msg = 'generate_captcha crashed while downloading the image.'
164
148
            logger.exception(msg)
165
149
            raise
166
 
        finally:
167
 
            wc.shutdown()
168
 
 
169
 
        defer.returnValue(captcha['captcha_id'])
170
 
 
171
 
    @defer.inlineCallbacks
 
150
 
 
151
        return captcha['captcha_id']
 
152
 
172
153
    def register_user(self, email, password, displayname,
173
154
                      captcha_id, captcha_solution):
174
155
        """Register a new user with 'email' and 'password'."""
175
156
        logger.debug('register_user: email: %r password: <hidden>, '
176
157
                     'displayname: %r, captcha_id: %r, captcha_solution: %r',
177
158
                     email, displayname, captcha_id, captcha_solution)
178
 
        restful_client = restful.RestfulClient(self.service_url)
179
 
        try:
180
 
            if not self._valid_email(email):
181
 
                logger.error('register_user: InvalidEmailError for email: %r',
182
 
                             email)
183
 
                raise InvalidEmailError()
184
 
            if not self._valid_password(password):
185
 
                logger.error('register_user: InvalidPasswordError')
186
 
                raise InvalidPasswordError()
 
159
        sso_service = self.sso_service_class(None, self.service_url)
 
160
        if not self._valid_email(email):
 
161
            logger.error('register_user: InvalidEmailError for email: %r',
 
162
                         email)
 
163
            raise InvalidEmailError()
 
164
        if not self._valid_password(password):
 
165
            logger.error('register_user: InvalidPasswordError')
 
166
            raise InvalidPasswordError()
187
167
 
188
 
            result = yield restful_client.restcall(u"registration.register",
189
 
                        email=email, password=password,
190
 
                        displayname=displayname,
191
 
                        captcha_id=captcha_id,
192
 
                        captcha_solution=captcha_solution)
193
 
        finally:
194
 
            restful_client.shutdown()
 
168
        result = sso_service.registrations.register(
 
169
                    email=email, password=password,
 
170
                    displayname=displayname,
 
171
                    captcha_id=captcha_id,
 
172
                    captcha_solution=captcha_solution)
195
173
        logger.info('register_user: email: %r result: %r', email, result)
196
174
 
197
175
        if result['status'].lower() == SSO_STATUS_ERROR:
200
178
        elif result['status'].lower() != SSO_STATUS_OK:
201
179
            raise RegistrationError('Received unknown status: %s' % result)
202
180
        else:
203
 
            defer.returnValue(email)
 
181
            return email
204
182
 
205
 
    @defer.inlineCallbacks
206
183
    def login(self, email, password, token_name):
207
184
        """Login a user with 'email' and 'password'."""
208
185
        logger.debug('login: email: %r password: <hidden>, token_name: %r',
209
186
                     email, token_name)
210
 
        restful_client = restful.RestfulClient(self.service_url,
211
 
                                               username=email,
212
 
                                               password=password)
 
187
        basic = BasicHttpAuthorizer(email, password)
 
188
        sso_service = self.sso_service_class(basic, self.service_url)
 
189
        service = sso_service.authentications.authenticate
 
190
 
213
191
        try:
214
 
            credentials = yield restful_client.restcall(
215
 
                        u"authentications.authenticate",
216
 
                        token_name=token_name)
217
 
        except WebClientError:
 
192
            credentials = service(token_name=token_name)
 
193
        except HTTPError:
218
194
            logger.exception('login failed with:')
219
195
            raise AuthenticationError()
220
 
        finally:
221
 
            restful_client.shutdown()
222
196
 
223
197
        logger.debug('login: authentication successful! consumer_key: %r, ' \
224
198
                     'token_name: %r', credentials['consumer_key'], token_name)
225
 
        defer.returnValue(credentials)
 
199
        return credentials
226
200
 
227
 
    @defer.inlineCallbacks
228
 
    def is_validated(self, token):
 
201
    def is_validated(self, token, sso_service=None):
229
202
        """Return if user with 'email' and 'password' is validated."""
230
203
        logger.debug('is_validated: requesting accounts.me() info.')
231
 
        restful_client = restful.RestfulClient(self.service_url,
232
 
                                               oauth_credentials=token)
233
 
        try:
234
 
            me_info = yield restful_client.restcall(u"accounts.me")
235
 
        finally:
236
 
            restful_client.shutdown()
 
204
        if sso_service is None:
 
205
            oauth_token = oauth.OAuthToken(token['token'],
 
206
                                           token['token_secret'])
 
207
            authorizer = TimestampedAuthorizer(
 
208
                                        timestamp_checker.get_faithful_time,
 
209
                                        token['consumer_key'],
 
210
                                        token['consumer_secret'],
 
211
                                        oauth_token)
 
212
            sso_service = self.sso_service_class(authorizer, self.service_url)
 
213
 
 
214
        me_info = sso_service.accounts.me()
237
215
        key = 'preferred_email'
238
216
        result = key in me_info and me_info[key] != None
239
217
 
240
218
        logger.info('is_validated: consumer_key: %r, result: %r.',
241
219
                    token['consumer_key'], result)
242
 
        defer.returnValue(result)
 
220
        return result
243
221
 
244
 
    @defer.inlineCallbacks
245
222
    def validate_email(self, email, password, email_token, token_name):
246
223
        """Validate an email token for user with 'email' and 'password'."""
247
224
        logger.debug('validate_email: email: %r password: <hidden>, '
248
225
                     'email_token: %r, token_name: %r.',
249
226
                     email, email_token, token_name)
250
 
        credentials = yield self.login(email=email, password=password,
251
 
                                       token_name=token_name)
252
 
        restful_client = restful.RestfulClient(self.service_url,
253
 
                                               oauth_credentials=credentials)
254
 
        try:
255
 
            result = yield restful_client.restcall(u"accounts.validate_email",
256
 
                                                   email_token=email_token)
257
 
        finally:
258
 
            restful_client.shutdown()
 
227
        token = self.login(email=email, password=password,
 
228
                           token_name=token_name)
 
229
 
 
230
        oauth_token = oauth.OAuthToken(token['token'], token['token_secret'])
 
231
        authorizer = TimestampedAuthorizer(timestamp_checker.get_faithful_time,
 
232
                                           token['consumer_key'],
 
233
                                           token['consumer_secret'],
 
234
                                           oauth_token)
 
235
        sso_service = self.sso_service_class(authorizer, self.service_url)
 
236
        result = sso_service.accounts.validate_email(email_token=email_token)
259
237
        logger.info('validate_email: email: %r result: %r', email, result)
260
238
        if 'errors' in result:
261
239
            errorsdict = self._format_webservice_errors(result['errors'])
262
240
            raise EmailTokenError(errorsdict)
263
241
        elif 'email' in result:
264
 
            defer.returnValue(credentials)
 
242
            return token
265
243
        else:
266
244
            raise EmailTokenError('Received invalid reply: %s' % result)
267
245
 
268
 
    @defer.inlineCallbacks
269
246
    def request_password_reset_token(self, email):
270
247
        """Request a token to reset the password for the account 'email'."""
271
 
        restful_client = restful.RestfulClient(self.service_url)
 
248
        sso_service = self.sso_service_class(None, self.service_url)
 
249
        service = sso_service.registrations.request_password_reset_token
272
250
        try:
273
 
            operation = u"registration.request_password_reset_token"
274
 
            result = yield restful_client.restcall(operation, email=email)
275
 
        except WebClientError, e:
 
251
            result = service(email=email)
 
252
        except HTTPError, e:
276
253
            logger.exception('request_password_reset_token failed with:')
277
 
            raise ResetPasswordTokenError(e[1].split('\n')[0])
278
 
        finally:
279
 
            restful_client.shutdown()
 
254
            raise ResetPasswordTokenError(e.content.split('\n')[0])
280
255
 
281
256
        if result['status'].lower() == SSO_STATUS_OK:
282
 
            defer.returnValue(email)
 
257
            return email
283
258
        else:
284
259
            raise ResetPasswordTokenError('Received invalid reply: %s' %
285
260
                                          result)
286
261
 
287
 
    @defer.inlineCallbacks
288
262
    def set_new_password(self, email, token, new_password):
289
263
        """Set a new password for the account 'email' to be 'new_password'.
290
264
 
292
266
        'request_password_reset_token'.
293
267
 
294
268
        """
295
 
        restful_client = restful.RestfulClient(self.service_url)
 
269
        sso_service = self.sso_service_class(None, self.service_url)
 
270
        service = sso_service.registrations.set_new_password
296
271
        try:
297
 
            result = yield restful_client.restcall(
298
 
                                        u"registration.set_new_password",
299
 
                                        email=email, token=token,
300
 
                                        new_password=new_password)
301
 
        except WebClientError, e:
 
272
            result = service(email=email, token=token,
 
273
                             new_password=new_password)
 
274
        except HTTPError, e:
302
275
            logger.exception('set_new_password failed with:')
303
 
            raise NewPasswordError(e[1].split('\n')[0])
304
 
        finally:
305
 
            restful_client.shutdown()
 
276
            raise NewPasswordError(e.content.split('\n')[0])
306
277
 
307
278
        if result['status'].lower() == SSO_STATUS_OK:
308
 
            defer.returnValue(email)
 
279
            return email
309
280
        else:
310
281
            raise NewPasswordError('Received invalid reply: %s' % result)