This was a big commit! I included lots of documentation below, but generally I
[mediagoblin.git] / mediagoblin / auth / views.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
24181820
CAW
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
342f06f7 17from itsdangerous import BadSignature
a77d952a 18
70f8b2d0 19from mediagoblin import messages, mg_globals
3fb96fc9 20from mediagoblin.db.models import User, Privilege
342f06f7 21from mediagoblin.tools.crypto import get_timed_signer_url
5adb906a 22from mediagoblin.decorators import auth_enabled, allow_registration
152a3bfa 23from mediagoblin.tools.response import render_to_response, redirect, render_404
a789b713 24from mediagoblin.tools.translate import pass_to_ugettext as _
02b6892c 25from mediagoblin.tools.mail import email_debug_message
e4deacd9 26from mediagoblin.tools.pluginapi import hook_handle
24181820 27from mediagoblin.auth import forms as auth_forms
f9e03221 28from mediagoblin.auth.tools import (send_verification_email, register_user,
bcd10ad6 29 send_fp_verification_email,
75fc9368 30 check_login_simple)
5784c12d 31from mediagoblin import auth
24181820 32
bf33272f 33
5adb906a
RE
34@allow_registration
35@auth_enabled
24181820 36def register(request):
a89df961
SS
37 """The registration view.
38
39 Note that usernames will always be lowercased. Email domains are lowercased while
40 the first part remains case-sensitive.
24181820 41 """
5784c12d 42 if 'pass_auth' not in request.template_env.globals:
e4deacd9 43 redirect_name = hook_handle('auth_no_pass_redirect')
4f8f0a4e
RE
44 if redirect_name:
45 return redirect(request, 'mediagoblin.plugins.{0}.register'.format(
46 redirect_name))
47 else:
48 return redirect(request, 'index')
13677ef9 49
e4deacd9 50 register_form = hook_handle("auth_get_registration_form", request)
24181820
CAW
51
52 if request.method == 'POST' and register_form.validate():
53 # TODO: Make sure the user doesn't exist already
f9e03221 54 user = register_user(request, register_form)
0bc03620 55
f9e03221 56 if user:
dce5c9cb
CAW
57 # redirect the user to their homepage... there will be a
58 # message waiting for them to verify their email
0bc03620
CAW
59 return redirect(
60 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 61 user=user.username)
24181820 62
9038c9f9
CAW
63 return render_to_response(
64 request,
c9c24934 65 'mediagoblin/auth/register.html',
57e8be21 66 {'register_form': register_form,
57e8be21 67 'post_url': request.urlgen('mediagoblin.auth.register')})
24181820
CAW
68
69
5adb906a 70@auth_enabled
692fd1c9 71def login(request):
a3776717 72 """
8e1e744d 73 MediaGoblin login view.
a3776717
CAW
74
75 If you provide the POST with 'next', it'll redirect to that view.
76 """
5784c12d 77 if 'pass_auth' not in request.template_env.globals:
e4deacd9 78 redirect_name = hook_handle('auth_no_pass_redirect')
4f8f0a4e
RE
79 if redirect_name:
80 return redirect(request, 'mediagoblin.plugins.{0}.login'.format(
81 redirect_name))
82 else:
83 return redirect(request, 'index')
5784c12d 84
e4deacd9 85 login_form = hook_handle("auth_get_login_form", request)
692fd1c9 86
a3776717
CAW
87 login_failed = False
88
69b56235 89 if request.method == 'POST':
bcd10ad6 90 username = login_form.username.data
b2c8dbcf 91
69b56235 92 if login_form.validate():
d1c9ef47 93 user = check_login_simple(username, login_form.password.data)
b2c8dbcf 94
75fc9368 95 if user:
69b56235 96 # set up login in session
527b7e3b 97 if login_form.stay_logged_in.data:
ef57b062 98 request.session['stay_logged_in'] = True
69b56235
SS
99 request.session['user_id'] = unicode(user.id)
100 request.session.save()
692fd1c9 101
69b56235
SS
102 if request.form.get('next'):
103 return redirect(request, location=request.form['next'])
104 else:
105 return redirect(request, "index")
692fd1c9 106
a3776717 107 login_failed = True
692fd1c9 108
9038c9f9
CAW
109 return render_to_response(
110 request,
c9c24934
E
111 'mediagoblin/auth/login.html',
112 {'login_form': login_form,
111a609d 113 'next': request.GET.get('next') or request.form.get('next'),
13bb1d67 114 'login_failed': login_failed,
57e8be21 115 'post_url': request.urlgen('mediagoblin.auth.login'),
13bb1d67 116 'allow_registration': mg_globals.app_config["allow_registration"]})
692fd1c9
CAW
117
118
119def logout(request):
b97232fa
CAW
120 # Maybe deleting the user_id parameter would be enough?
121 request.session.delete()
7b31a11c 122
9150244a 123 return redirect(request, "index")
db1a438f 124
5866d1a8 125
db1a438f 126def verify_email(request):
4c093e85
JW
127 """
128 Email verification view
129
130 validates GET parameters against database and unlocks the user account, if
131 you are lucky :)
132 """
155f24f9 133 # If we don't have userid and token parameters, we can't do anything; 404
342f06f7 134 if not 'token' in request.GET:
de12b4e7 135 return render_404(request)
155f24f9 136
342f06f7
RE
137 # Catch error if token is faked or expired
138 try:
139 token = get_timed_signer_url("mail_verification_token") \
140 .loads(request.GET['token'], max_age=10*24*3600)
141 except BadSignature:
142 messages.add_message(
143 request,
144 messages.ERROR,
145 _('The verification key or user id is incorrect.'))
db1a438f 146
342f06f7
RE
147 return redirect(
148 request,
149 'index')
150
151 user = User.query.filter_by(id=int(token)).first()
db1a438f 152
342f06f7 153 if user and user.email_verified is False:
7a3d00ec 154 user.status = u'active'
4facc7a0 155 user.email_verified = True
00bb9550 156 user.verification_key = None
3fb96fc9 157 user.all_privileges.append(
158 Privilege.query.filter(
159 Privilege.privilege_name==u'active').first())
daf02964 160
db1a438f 161 user.save()
daf02964 162
fe80cb06 163 messages.add_message(
7b31a11c
CAW
164 request,
165 messages.SUCCESS,
4b1adc13
CAW
166 _("Your email address has been verified. "
167 "You may now login, edit your profile, and submit images!"))
db1a438f 168 else:
4b1adc13
CAW
169 messages.add_message(
170 request,
171 messages.ERROR,
172 _('The verification key or user id is incorrect'))
7b31a11c 173
269943a6
CAW
174 return redirect(
175 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 176 user=user.username)
28afb47c 177
5866d1a8 178
b93a6a22
AM
179def resend_activation(request):
180 """
181 The reactivation view
182
183 Resend the activation email.
184 """
84a7e770 185
2fe69916 186 if request.user is None:
7903a14f
AW
187 messages.add_message(
188 request,
189 messages.ERROR,
2fe69916 190 _('You must be logged in so we know who to send the email to!'))
dfa6994d 191
5dbeda8a 192 return redirect(request, 'mediagoblin.auth.login')
7903a14f 193
0ab21f98 194 if request.user.email_verified:
84a7e770
AW
195 messages.add_message(
196 request,
197 messages.ERROR,
2fe69916 198 _("You've already verified your email address!"))
dfa6994d 199
2fe69916 200 return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
84a7e770 201
bf33272f 202 email_debug_message(request)
02d80437 203 send_verification_email(request.user, request)
b93a6a22 204
61927e6e
CAW
205 messages.add_message(
206 request,
207 messages.INFO,
4b1adc13 208 _('Resent your verification email.'))
61927e6e
CAW
209 return redirect(
210 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 211 user=request.user.username)
25ba955e
AV
212
213
214def forgot_password(request):
215 """
216 Forgot password view
217
a89df961
SS
218 Sends an email with an url to renew forgotten password.
219 Use GET querystring parameter 'username' to pre-populate the input field
25ba955e 220 """
f339b76a
RE
221 if not 'pass_auth' in request.template_env.globals:
222 return redirect(request, 'index')
223
111a609d 224 fp_form = auth_forms.ForgotPassForm(request.form,
a89df961
SS
225 username=request.args.get('username'))
226
227 if not (request.method == 'POST' and fp_form.validate()):
228 # Either GET request, or invalid form submitted. Display the template
229 return render_to_response(request,
e4deacd9 230 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form,})
a89df961
SS
231
232 # If we are here: method == POST and form is valid. username casing
233 # has been sanitized. Store if a user was found by email. We should
234 # not reveal if the operation was successful then as we don't want to
235 # leak if an email address exists in the system.
9d140cb8 236 found_by_email = '@' in fp_form.username.data
a89df961
SS
237
238 if found_by_email:
239 user = User.query.filter_by(
9d140cb8 240 email = fp_form.username.data).first()
a89df961
SS
241 # Don't reveal success in case the lookup happened by email address.
242 success_message=_("If that email address (case sensitive!) is "
243 "registered an email has been sent with instructions "
244 "on how to change your password.")
245
246 else: # found by username
247 user = User.query.filter_by(
9d140cb8 248 username = fp_form.username.data).first()
a89df961
SS
249
250 if user is None:
251 messages.add_message(request,
252 messages.WARNING,
253 _("Couldn't find someone with that username."))
254 return redirect(request, 'mediagoblin.auth.forgot_password')
25ba955e 255
a89df961
SS
256 success_message=_("An email has been sent with instructions "
257 "on how to change your password.")
bf33272f 258
a89df961
SS
259 if user and not(user.email_verified and user.status == 'active'):
260 # Don't send reminder because user is inactive or has no verified email
261 messages.add_message(request,
262 messages.WARNING,
263 _("Could not send password recovery email as your username is in"
264 "active or your account's email address has not been verified."))
25ba955e 265
a89df961
SS
266 return redirect(request, 'mediagoblin.user_pages.user_home',
267 user=user.username)
a85a2110 268
a89df961
SS
269 # SUCCESS. Send reminder and return to login page
270 if user:
a89df961
SS
271 email_debug_message(request)
272 send_fp_verification_email(user, request)
992e4f80 273
a89df961
SS
274 messages.add_message(request, messages.INFO, success_message)
275 return redirect(request, 'mediagoblin.auth.login')
25ba955e
AV
276
277
278def verify_forgot_password(request):
961fe381
CAW
279 """
280 Check the forgot-password verification and possibly let the user
281 change their password because of it.
282 """
f7ab6670
CAW
283 # get form data variables, and specifically check for presence of token
284 formdata = _process_for_token(request)
342f06f7 285 if not formdata['has_token']:
8d1c9863
CFD
286 return render_404(request)
287
f7ab6670 288 formdata_vars = formdata['vars']
8d1c9863 289
342f06f7
RE
290 # Catch error if token is faked or expired
291 try:
292 token = get_timed_signer_url("mail_verification_token") \
293 .loads(formdata_vars['token'], max_age=10*24*3600)
294 except BadSignature:
295 messages.add_message(
296 request,
297 messages.ERROR,
298 _('The verification key or user id is incorrect.'))
299
300 return redirect(
301 request,
302 'index')
303
70f8b2d0 304 # check if it's a valid user id
342f06f7
RE
305 user = User.query.filter_by(id=int(token)).first()
306
307 # no user in db
70f8b2d0 308 if not user:
342f06f7
RE
309 messages.add_message(
310 request, messages.ERROR,
311 _('The user id is incorrect.'))
312 return redirect(
313 request, 'index')
8d1c9863 314
342f06f7
RE
315 # check if user active and has email verified
316 if user.email_verified and user.status == 'active':
73fffbb8 317
f7ab6670 318 cp_form = auth_forms.ChangePassForm(formdata_vars)
8d1c9863
CFD
319
320 if request.method == 'POST' and cp_form.validate():
cdc6b571 321 user.pw_hash = auth.gen_password_hash(
9d140cb8 322 cp_form.password.data)
8d1c9863
CFD
323 user.save()
324
35149b11
JS
325 messages.add_message(
326 request,
327 messages.INFO,
328 _("You can now log in using your new password."))
329 return redirect(request, 'mediagoblin.auth.login')
25ba955e 330 else:
73fffbb8
CAW
331 return render_to_response(
332 request,
333 'mediagoblin/auth/change_fp.html',
e4deacd9 334 {'cp_form': cp_form,})
73fffbb8 335
342f06f7
RE
336 if not user.email_verified:
337 messages.add_message(
338 request, messages.ERROR,
339 _('You need to verify your email before you can reset your'
340 ' password.'))
341
342 if not user.status == 'active':
343 messages.add_message(
344 request, messages.ERROR,
345 _('You are no longer an active user. Please contact the system'
346 ' admin to reactivate your accoutn.'))
347
348 return redirect(
349 request, 'index')
8d1c9863
CFD
350
351
352def _process_for_token(request):
353 """
f7ab6670 354 Checks for tokens in formdata without prior knowledge of request method
8d1c9863 355
f7ab6670
CAW
356 For now, returns whether the userid and token formdata variables exist, and
357 the formdata variables in a hash. Perhaps an object is warranted?
8d1c9863 358 """
f7ab6670 359 # retrieve the formdata variables
8d1c9863 360 if request.method == 'GET':
f7ab6670 361 formdata_vars = request.GET
8d1c9863 362 else:
111a609d 363 formdata_vars = request.form
8d1c9863 364
f7ab6670
CAW
365 formdata = {
366 'vars': formdata_vars,
342f06f7 367 'has_token': 'token' in formdata_vars}
2c9e8184 368
f7ab6670 369 return formdata