1
# Copyright 2010 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
6
from urlparse import urljoin
8
import django.contrib.auth as auth
10
from django.conf import settings
11
from django.contrib.auth.decorators import login_required
12
from django.core.mail import send_mail
13
from django.core.urlresolvers import Resolver404, resolve, reverse
14
from django.http import Http404, HttpResponseRedirect, HttpResponseNotFound
15
from django.shortcuts import (get_object_or_404, get_list_or_404,
17
from django.template import RequestContext
18
from django.utils.translation import ugettext as _
19
from django.template.loader import render_to_string
21
from identityprovider.branding import current_brand
22
from identityprovider.decorators import guest_required, dont_cache, limitlogin
23
from identityprovider.forms import (LoginForm, ForgotPasswordForm,
24
ResetPasswordForm, NewAccountForm, ConfirmNewAccountForm)
25
from identityprovider.models import (Account, AuthToken, AuthTokenFactory,
27
from identityprovider.models.const import EmailStatus, LoginTokenType
28
import identityprovider.signed as signed
29
from identityprovider.utils import (encrypt_launchpad_password,
30
format_address, get_person_and_account_by_email, polite_form_errors,
31
CannotResetPasswordException, PersonAndAccountNotFoundException)
32
from identityprovider.decorators import check_readonly
33
from identityprovider.views.utils import get_rpconfig
34
from identityprovider.models.captcha import Captcha
37
logger = logging.getLogger('sso')
43
def login(request, token=None, rpconfig=None):
45
if request.method == 'POST':
46
form = LoginForm(request.POST)
48
username = form.cleaned_data['email']
49
password = form.cleaned_data['password']
50
user = auth.authenticate(username=username, password=password)
51
auth.login(request, user)
52
next = request.POST.get('next')
53
limitlogin().reset_count(request)
54
if next and _is_safe_redirect_url(next):
55
return HttpResponseRedirect(next)
57
return HttpResponseRedirect('/%s/' % token)
59
return HttpResponseRedirect('/')
61
polite_form_errors(form._errors)
62
context = RequestContext(request, {
64
'next': request.GET.get('next'),
66
'token': token is not None,
67
'message': request.session.get('message', None),
68
'message_style': 'informational',
71
del request.session['message']
74
return render_to_response('registration/login.html', context)
77
def _is_safe_redirect_url(url):
78
""" Check whether 'url' resolves into a valid URL this application can
81
if url.find('?') >= 0:
82
url = url[:url.find('?')]
83
response = resolve(url)
88
view, args, kwargs = response
92
def logout(request, token=None):
93
# We don't want to lose session[token] when we log the user out
94
raw_orequest = request.session.get(token, None)
96
if token is not None and raw_orequest is not None:
97
request.session[token] = raw_orequest
98
request.session['message'] = _("You have successfully logged out")
99
return HttpResponseRedirect('+login')
102
def claim_token(request, authtoken):
103
atrequest = get_object_or_404(AuthToken, token=authtoken)
104
if atrequest.token_type == LoginTokenType.PASSWORDRECOVERY:
105
return HttpResponseRedirect('/token/%s/+resetpassword' % authtoken)
106
elif atrequest.token_type == LoginTokenType.NEWPERSONLESSACCOUNT:
107
return HttpResponseRedirect('/token/%s/+newaccount' % authtoken)
108
elif atrequest.token_type == LoginTokenType.VALIDATEEMAIL:
109
return HttpResponseRedirect('/token/%s/+newemail' % authtoken)
116
def new_account(request, token=None):
117
form = NewAccountForm()
118
if request.method == 'POST':
119
form = NewAccountForm(request.POST)
122
response = _verify_captcha_response(
123
'registration/new_account.html', request, form)
127
email = form.cleaned_data['email']
128
already_exists = True
130
person, account = get_person_and_account_by_email(email)
131
except (CannotResetPasswordException,
132
PersonAndAccountNotFoundException):
133
already_exists = False
135
if account is not None and account.is_active:
136
# only send out email if account is active
137
# otherwise disabled account can be spammed
139
'forgotten_password_url':
140
urljoin(settings.SSO_ROOT_URL, '+forgot_password'),
141
'sso_brand_name': current_brand.name,
143
message = render_to_string(
144
'email/impersonate-warning.txt', replacements)
145
subject = current_brand.name + ": " + _("Warning")
146
from_address = format_address(
147
settings.NOREPLY_FROM_ADDRESS, current_brand.name)
148
send_mail(subject, message, from_address, [email])
150
# log why email was not sent
152
condition = ("account for person '%s' does not exist" %
155
condition = ("account '%s' is not active" %
157
logger.debug("In view 'new_account' email was not sent "
158
"out because %s" % condition)
160
view, args, kwargs = resolve(request.get_full_path())
161
if token is not None:
162
# Redirected from an OpenID request, must get back after
164
redirection_url = '/%s/+decide' % token
166
redirection_url = '/'
167
token = AuthTokenFactory().new(
168
requester=None, requester_email=None, email=email,
169
token_type=LoginTokenType.NEWPERSONLESSACCOUNT,
170
redirection_url=redirection_url)
171
token.sendNewUserEmail()
172
request.session['token_newaccount'] = token.token
173
request.session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
174
request.session['email_heading'] = _("Registration mail sent")
175
request.session['email_reason'] = _("We’ve just emailed "
176
"%(email_to)s (from %(email_from)s) to confirm "
179
'email_from': settings.NOREPLY_FROM_ADDRESS,
181
request.session['email_notreceived_extra'] = None
182
return HttpResponseRedirect('/+email-sent')
184
polite_form_errors(form._errors)
186
return render_to_response('registration/new_account.html',
187
RequestContext(request, {'form': form,
188
'CAPTCHA_PUBLIC_KEY': settings.CAPTCHA_PUBLIC_KEY,
192
def _verify_captcha_response(template, request, form):
193
captcha = Captcha(request.environ,
194
request.POST.get('recaptcha_challenge_field'))
195
captcha_solution = request.POST.get('recaptcha_response_field')
196
verified = captcha.verify(captcha_solution)
198
return render_to_response(template,
199
RequestContext(request, {'form': form,
200
'CAPTCHA_PUBLIC_KEY': settings.CAPTCHA_PUBLIC_KEY,
201
'captcha_error': ('&error=%s' % captcha.message),
208
def confirm_account(request, authtoken):
209
atrequest = get_object_or_404(AuthToken, token=authtoken,
210
token_type=LoginTokenType.NEWPERSONLESSACCOUNT)
211
session_token = request.session.get('token_newaccount')
212
if session_token is None or session_token != atrequest.token:
213
return HttpResponseRedirect('/+bad-token')
214
form = ConfirmNewAccountForm()
215
if request.method == 'POST':
216
form = ConfirmNewAccountForm(request.POST)
218
username = atrequest.email
219
password = form.cleaned_data['password']
220
displayname = form.cleaned_data['displayname']
221
creation_rationale = None
222
if atrequest.redirection_url.endswith('+decide'):
223
# Try to determine creation rationale from RP config
224
view, args, kwargs = resolve(atrequest.redirection_url)
225
if 'token' in kwargs:
227
raw_orequest = request.session.get(
228
kwargs['token'], None)
229
openid_request = signed.loads(
230
raw_orequest, settings.SECRET_KEY)
232
rpconfig = get_rpconfig(openid_request.trust_root)
233
if rpconfig is not None:
234
creation_rationale = rpconfig.creation_rationale
237
Account.objects.create_account(
238
displayname, username, password, creation_rationale)
239
user = auth.authenticate(username=username, password=password)
240
auth.login(request, user)
242
request.session['message'] = _("Your account was created "\
244
return HttpResponseRedirect(atrequest.redirection_url)
245
return render_to_response('registration/confirm_new_account.html',
246
RequestContext(request, {'form': form}))
250
def confirm_email(request, authtoken):
251
atrequest = get_object_or_404(AuthToken, token=authtoken,
252
token_type=LoginTokenType.VALIDATEEMAIL)
253
email = get_object_or_404(EmailAddress, email__iexact=atrequest.email)
255
if request.user.id != email.account.id:
259
if request.method == 'POST':
260
# only confirm the email address if the form was submitted
261
email.status = EmailStatus.VALIDATED
264
request.session['message'] = _("The email address %s has been "
266
return HttpResponseRedirect(atrequest.redirection_url)
268
# form was not submitted
269
return render_to_response('account/confirm_new_email.html',
270
RequestContext(request, {'email': email.email}))
273
def token_from_email(request, email):
274
if getattr(settings, 'ENABLE_TOKEN_DEBUG', False):
275
tokens = get_list_or_404(AuthToken, email=email)
276
return render_to_response('registration/token.html',
277
RequestContext(request, {'token': tokens[0].token}))
279
return HttpResponseNotFound()
282
def email_sent(request, token=None):
283
context = RequestContext(request, {
284
'email_feedback': request.session.get('email_feedback'),
285
'email_heading': request.session.get('email_heading'),
286
'email_reason': request.session.get('email_reason'),
287
'email_notreceived_extra': request.session.get(
288
'email_notreceived_extra'),
292
return render_to_response('registration/email_sent.html', context)
295
def bad_token(request):
296
context = RequestContext(request, {
297
'email_feedback': settings.FEEDBACK_TO_ADDRESS,
299
return render_to_response('registration/bad_token.html', context)
302
def deactivated(request):
303
context = RequestContext(request, {
304
'message': request.session.pop('message', None),
305
'message_style': 'informational'
307
return render_to_response('account/deactivated.html', context)
312
def forgot_password(request, token=None):
313
form = ForgotPasswordForm()
314
if request.method == 'POST':
315
form = ForgotPasswordForm(request.POST)
317
response = _verify_captcha_response(
318
'registration/forgot_password.html', request, form)
321
email = form.cleaned_data['email']
324
person, account = get_person_and_account_by_email(email)
325
if account is not None and not account.is_active:
327
# log why email was not sent
328
condition = ("account '%s' is not active" %
330
logger.debug("In view 'forgot_password' email was not "
331
"sent out because %s" % condition)
332
except (CannotResetPasswordException,
333
PersonAndAccountNotFoundException):
335
view, args, kwargs = resolve(request.get_full_path())
337
if token is not None:
338
# Redirected from an OpenID request, must get back after
340
redirection_url = reverse(
341
'identityprovider.views.server.decide',
342
kwargs={'token': token})
344
redirection_url = '/'
345
token = AuthTokenFactory().new(
346
account, email, email, LoginTokenType.PASSWORDRECOVERY,
347
redirection_url=redirection_url)
348
token.sendPasswordResetEmail()
349
request.session['token_forgotpassword'] = token.token
350
request.session['email_feedback'] = settings.FEEDBACK_TO_ADDRESS
351
request.session['email_heading'] = _("Forgotten your password?")
352
request.session['email_reason'] = _("We’ve just emailed "
353
"%(email_to)s (from %(email_from)s) with "
354
"instructions on resetting your password.") % {
356
'email_from': settings.NOREPLY_FROM_ADDRESS,
358
request.session['email_notreceived_extra'] = _("Check that "
359
"you’ve actually entered a subscribed email address.")
360
return HttpResponseRedirect('/+email-sent')
362
polite_form_errors(form._errors)
364
return render_to_response('registration/forgot_password.html',
365
RequestContext(request, {
367
'CAPTCHA_PUBLIC_KEY': settings.CAPTCHA_PUBLIC_KEY,
372
def reset_password(request, authtoken):
373
atrequest = get_object_or_404(AuthToken, token=authtoken,
374
token_type=LoginTokenType.PASSWORDRECOVERY)
375
account = atrequest.requester
376
session_token = request.session.get('token_forgotpassword')
377
if (session_token is None or session_token != atrequest.token or
378
not account.is_active):
379
# we hide the fact the the account is inactive, to avoid
380
# exposing valid accounts
381
return HttpResponseRedirect('/+bad-token')
382
if request.method == 'POST':
383
form = ResetPasswordForm(request.POST)
385
password = form.cleaned_data['password']
386
if account.preferredemail is None:
387
request.session['message'] = _(
388
'You cannot reset the password of a deactivated account.')
389
return HttpResponseRedirect('/+deactivated')
390
email = account.preferredemail.email
391
password_obj = account.accountpassword
392
password_obj.password = encrypt_launchpad_password(password)
394
user = auth.authenticate(username=email, password=password)
395
auth.login(request, user)
397
return HttpResponseRedirect(atrequest.redirection_url)
399
form = ResetPasswordForm()
400
return render_to_response('registration/reset_password.html',
401
RequestContext(request, {'form': form}))
404
def static_page(request, page_name):
405
return render_to_response('static/%s.html' % page_name,
406
RequestContext(request))