Commit | Line | Data |
---|---|---|
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 | 17 | from itsdangerous import BadSignature |
a77d952a | 18 | |
70f8b2d0 | 19 | from mediagoblin import messages, mg_globals |
3fb96fc9 | 20 | from mediagoblin.db.models import User, Privilege |
342f06f7 | 21 | from mediagoblin.tools.crypto import get_timed_signer_url |
5adb906a | 22 | from mediagoblin.decorators import auth_enabled, allow_registration |
152a3bfa | 23 | from mediagoblin.tools.response import render_to_response, redirect, render_404 |
a789b713 | 24 | from mediagoblin.tools.translate import pass_to_ugettext as _ |
02b6892c | 25 | from mediagoblin.tools.mail import email_debug_message |
e4deacd9 | 26 | from mediagoblin.tools.pluginapi import hook_handle |
24181820 | 27 | from mediagoblin.auth import forms as auth_forms |
f9e03221 | 28 | from mediagoblin.auth.tools import (send_verification_email, register_user, |
bcd10ad6 | 29 | send_fp_verification_email, |
75fc9368 | 30 | check_login_simple) |
5784c12d | 31 | from mediagoblin import auth |
24181820 | 32 | |
bf33272f | 33 | |
5adb906a RE |
34 | @allow_registration |
35 | @auth_enabled | |
24181820 | 36 | def 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 | 71 | def 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 | ||
119 | def 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 | 126 | def 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 |
179 | def 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 | ||
214 | def 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 | ||
278 | def 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 | ||
352 | def _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 |