Rename save button to 'save as draft'.
[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
b0c8328e 20from mediagoblin.db.models import User
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 _
dd39fe60 25from mediagoblin.tools.mail import email_debug_message
e4deacd9 26from mediagoblin.tools.pluginapi import hook_handle
5784c12d 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,
1d321f1c 30 check_login_simple)
5784c12d 31from mediagoblin import auth
bf33272f
E
32
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')
5784c12d 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
5784c12d 54 user = register_user(request, register_form)
0bc03620 55
5784c12d 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
0578d8b3 92 if login_form.validate():
d1c9ef47 93 user = check_login_simple(username, login_form.password.data)
692fd1c9 94
1d321f1c 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()
152
153 if user and user.email_verified is False:
7a3d00ec 154 user.status = u'active'
4facc7a0 155 user.email_verified = True
db1a438f 156 user.save()
daf02964 157
fe80cb06 158 messages.add_message(
7b31a11c
CAW
159 request,
160 messages.SUCCESS,
4b1adc13
CAW
161 _("Your email address has been verified. "
162 "You may now login, edit your profile, and submit images!"))
db1a438f 163 else:
4b1adc13
CAW
164 messages.add_message(
165 request,
166 messages.ERROR,
167 _('The verification key or user id is incorrect'))
7b31a11c 168
269943a6
CAW
169 return redirect(
170 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 171 user=user.username)
28afb47c 172
5866d1a8 173
b93a6a22
AM
174def resend_activation(request):
175 """
176 The reactivation view
177
178 Resend the activation email.
179 """
84a7e770 180
2fe69916 181 if request.user is None:
7903a14f
AW
182 messages.add_message(
183 request,
184 messages.ERROR,
2fe69916 185 _('You must be logged in so we know who to send the email to!'))
dfa6994d 186
5dbeda8a 187 return redirect(request, 'mediagoblin.auth.login')
7903a14f 188
0ab21f98 189 if request.user.email_verified:
84a7e770
AW
190 messages.add_message(
191 request,
192 messages.ERROR,
2fe69916 193 _("You've already verified your email address!"))
dfa6994d 194
2fe69916 195 return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
84a7e770 196
bf33272f 197 email_debug_message(request)
02d80437 198 send_verification_email(request.user, request)
b93a6a22 199
61927e6e
CAW
200 messages.add_message(
201 request,
202 messages.INFO,
4b1adc13 203 _('Resent your verification email.'))
61927e6e
CAW
204 return redirect(
205 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 206 user=request.user.username)
14efa7bd
RE
207
208
209def forgot_password(request):
210 """
211 Forgot password view
212
213 Sends an email with an url to renew forgotten password.
214 Use GET querystring parameter 'username' to pre-populate the input field
215 """
f339b76a
RE
216 if not 'pass_auth' in request.template_env.globals:
217 return redirect(request, 'index')
218
14efa7bd
RE
219 fp_form = auth_forms.ForgotPassForm(request.form,
220 username=request.args.get('username'))
221
222 if not (request.method == 'POST' and fp_form.validate()):
223 # Either GET request, or invalid form submitted. Display the template
224 return render_to_response(request,
e4deacd9 225 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form,})
14efa7bd
RE
226
227 # If we are here: method == POST and form is valid. username casing
228 # has been sanitized. Store if a user was found by email. We should
229 # not reveal if the operation was successful then as we don't want to
230 # leak if an email address exists in the system.
231 found_by_email = '@' in fp_form.username.data
232
233 if found_by_email:
234 user = User.query.filter_by(
235 email = fp_form.username.data).first()
236 # Don't reveal success in case the lookup happened by email address.
237 success_message=_("If that email address (case sensitive!) is "
238 "registered an email has been sent with instructions "
239 "on how to change your password.")
240
241 else: # found by username
242 user = User.query.filter_by(
243 username = fp_form.username.data).first()
244
245 if user is None:
246 messages.add_message(request,
247 messages.WARNING,
248 _("Couldn't find someone with that username."))
249 return redirect(request, 'mediagoblin.auth.forgot_password')
250
251 success_message=_("An email has been sent with instructions "
252 "on how to change your password.")
253
254 if user and not(user.email_verified and user.status == 'active'):
255 # Don't send reminder because user is inactive or has no verified email
256 messages.add_message(request,
257 messages.WARNING,
258 _("Could not send password recovery email as your username is in"
259 "active or your account's email address has not been verified."))
260
261 return redirect(request, 'mediagoblin.user_pages.user_home',
262 user=user.username)
263
264 # SUCCESS. Send reminder and return to login page
265 if user:
14efa7bd
RE
266 email_debug_message(request)
267 send_fp_verification_email(user, request)
268
269 messages.add_message(request, messages.INFO, success_message)
270 return redirect(request, 'mediagoblin.auth.login')
271
272
273def verify_forgot_password(request):
274 """
275 Check the forgot-password verification and possibly let the user
276 change their password because of it.
277 """
278 # get form data variables, and specifically check for presence of token
279 formdata = _process_for_token(request)
342f06f7 280 if not formdata['has_token']:
14efa7bd
RE
281 return render_404(request)
282
14efa7bd
RE
283 formdata_vars = formdata['vars']
284
342f06f7
RE
285 # Catch error if token is faked or expired
286 try:
287 token = get_timed_signer_url("mail_verification_token") \
288 .loads(formdata_vars['token'], max_age=10*24*3600)
289 except BadSignature:
290 messages.add_message(
291 request,
292 messages.ERROR,
293 _('The verification key or user id is incorrect.'))
294
295 return redirect(
296 request,
297 'index')
298
14efa7bd 299 # check if it's a valid user id
342f06f7
RE
300 user = User.query.filter_by(id=int(token)).first()
301
302 # no user in db
14efa7bd 303 if not user:
342f06f7
RE
304 messages.add_message(
305 request, messages.ERROR,
306 _('The user id is incorrect.'))
307 return redirect(
308 request, 'index')
14efa7bd 309
342f06f7
RE
310 # check if user active and has email verified
311 if user.email_verified and user.status == 'active':
14efa7bd
RE
312
313 cp_form = auth_forms.ChangePassForm(formdata_vars)
314
315 if request.method == 'POST' and cp_form.validate():
cdc6b571 316 user.pw_hash = auth.gen_password_hash(
14efa7bd 317 cp_form.password.data)
14efa7bd
RE
318 user.save()
319
320 messages.add_message(
321 request,
322 messages.INFO,
323 _("You can now log in using your new password."))
324 return redirect(request, 'mediagoblin.auth.login')
325 else:
326 return render_to_response(
327 request,
328 'mediagoblin/auth/change_fp.html',
e4deacd9 329 {'cp_form': cp_form,})
14efa7bd 330
342f06f7
RE
331 if not user.email_verified:
332 messages.add_message(
333 request, messages.ERROR,
334 _('You need to verify your email before you can reset your'
335 ' password.'))
336
337 if not user.status == 'active':
338 messages.add_message(
339 request, messages.ERROR,
340 _('You are no longer an active user. Please contact the system'
341 ' admin to reactivate your accoutn.'))
342
343 return redirect(
344 request, 'index')
14efa7bd
RE
345
346
347def _process_for_token(request):
348 """
349 Checks for tokens in formdata without prior knowledge of request method
350
351 For now, returns whether the userid and token formdata variables exist, and
352 the formdata variables in a hash. Perhaps an object is warranted?
353 """
354 # retrieve the formdata variables
355 if request.method == 'GET':
356 formdata_vars = request.GET
357 else:
358 formdata_vars = request.form
359
360 formdata = {
361 'vars': formdata_vars,
342f06f7 362 'has_token': 'token' in formdata_vars}
14efa7bd
RE
363
364 return formdata