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/>.
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
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.
34
All the methods in Account expect unicode as parameters.
19
"""Single Sign On account management."""
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
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
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'
109
96
class Account(object):
110
97
"""Login and register users using the Ubuntu Single Sign On service."""
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
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
106
self.service_url = os.environ.get('USSOC_SERVICE_URL', SERVICE_URL)
108
logger.info('Created a new SSO access layer for service url %r',
119
109
self.service_url)
120
assert self.service_url.endswith("/")
122
111
def _valid_email(self, email):
123
112
"""Validate the given email."""
141
130
result[key] = "\n".join(val)
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',
149
restful_client = restful.RestfulClient(self.service_url)
151
captcha = yield restful_client.restcall(u"captchas.new")
153
restful_client.shutdown()
137
sso_service = self.sso_service_class(None, self.service_url)
138
captcha = sso_service.captchas.new()
155
140
# download captcha and save to 'filename'
156
141
logger.debug('generate_captcha: server answered: %r', captcha)
157
wc = webclient.webclient_factory()
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)
163
147
msg = 'generate_captcha crashed while downloading the image.'
164
148
logger.exception(msg)
169
defer.returnValue(captcha['captcha_id'])
171
@defer.inlineCallbacks
151
return captcha['captcha_id']
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)
180
if not self._valid_email(email):
181
logger.error('register_user: InvalidEmailError for email: %r',
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',
163
raise InvalidEmailError()
164
if not self._valid_password(password):
165
logger.error('register_user: InvalidPasswordError')
166
raise InvalidPasswordError()
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)
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)
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)
203
defer.returnValue(email)
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,
187
basic = BasicHttpAuthorizer(email, password)
188
sso_service = self.sso_service_class(basic, self.service_url)
189
service = sso_service.authentications.authenticate
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)
218
194
logger.exception('login failed with:')
219
195
raise AuthenticationError()
221
restful_client.shutdown()
223
197
logger.debug('login: authentication successful! consumer_key: %r, ' \
224
198
'token_name: %r', credentials['consumer_key'], token_name)
225
defer.returnValue(credentials)
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)
234
me_info = yield restful_client.restcall(u"accounts.me")
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'],
212
sso_service = self.sso_service_class(authorizer, self.service_url)
214
me_info = sso_service.accounts.me()
237
215
key = 'preferred_email'
238
216
result = key in me_info and me_info[key] != None
240
218
logger.info('is_validated: consumer_key: %r, result: %r.',
241
219
token['consumer_key'], result)
242
defer.returnValue(result)
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)
255
result = yield restful_client.restcall(u"accounts.validate_email",
256
email_token=email_token)
258
restful_client.shutdown()
227
token = self.login(email=email, password=password,
228
token_name=token_name)
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'],
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)
266
244
raise EmailTokenError('Received invalid reply: %s' % result)
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
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)
276
253
logger.exception('request_password_reset_token failed with:')
277
raise ResetPasswordTokenError(e[1].split('\n')[0])
279
restful_client.shutdown()
254
raise ResetPasswordTokenError(e.content.split('\n')[0])
281
256
if result['status'].lower() == SSO_STATUS_OK:
282
defer.returnValue(email)
284
259
raise ResetPasswordTokenError('Received invalid reply: %s' %
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'.
292
266
'request_password_reset_token'.
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
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)
302
275
logger.exception('set_new_password failed with:')
303
raise NewPasswordError(e[1].split('\n')[0])
305
restful_client.shutdown()
276
raise NewPasswordError(e.content.split('\n')[0])
307
278
if result['status'].lower() == SSO_STATUS_OK:
308
defer.returnValue(email)
310
281
raise NewPasswordError('Received invalid reply: %s' % result)