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 |
b0c8328e | 20 | from mediagoblin.db.models import User |
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 _ |
dd39fe60 | 25 | from mediagoblin.tools.mail import email_debug_message |
e4deacd9 | 26 | from mediagoblin.tools.pluginapi import hook_handle |
5784c12d | 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, |
1d321f1c | 30 | check_login_simple) |
5784c12d | 31 | from mediagoblin import auth |
bf33272f E |
32 | |
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') | |
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 | 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 | |
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 | ||
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() | |
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 |
174 | def 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 | ||
209 | def 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 | ||
273 | def 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 | ||
347 | def _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 |