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 | ||
a77d952a | 17 | import uuid |
25ba955e | 18 | import datetime |
a77d952a | 19 | |
70f8b2d0 | 20 | from mediagoblin import messages, mg_globals |
b0c8328e | 21 | from mediagoblin.db.models import User |
152a3bfa | 22 | from mediagoblin.tools.response import render_to_response, redirect, render_404 |
a789b713 | 23 | from mediagoblin.tools.translate import pass_to_ugettext as _ |
02b6892c | 24 | from mediagoblin.tools.mail import email_debug_message |
24181820 CAW |
25 | from mediagoblin.auth import lib as auth_lib |
26 | from mediagoblin.auth import forms as auth_forms | |
97aebda7 | 27 | from mediagoblin.auth.lib import send_fp_verification_email |
f9e03221 | 28 | from mediagoblin.auth.tools import (send_verification_email, register_user, |
75fc9368 | 29 | check_login_simple) |
24181820 | 30 | |
bf33272f | 31 | |
24181820 | 32 | def register(request): |
a89df961 SS |
33 | """The registration view. |
34 | ||
35 | Note that usernames will always be lowercased. Email domains are lowercased while | |
36 | the first part remains case-sensitive. | |
24181820 | 37 | """ |
13677ef9 RL |
38 | # Redirects to indexpage if registrations are disabled |
39 | if not mg_globals.app_config["allow_registration"]: | |
166dc91a CAW |
40 | messages.add_message( |
41 | request, | |
42 | messages.WARNING, | |
4b1adc13 | 43 | _('Sorry, registration is disabled on this instance.')) |
13677ef9 RL |
44 | return redirect(request, "index") |
45 | ||
111a609d | 46 | register_form = auth_forms.RegistrationForm(request.form) |
24181820 CAW |
47 | |
48 | if request.method == 'POST' and register_form.validate(): | |
49 | # TODO: Make sure the user doesn't exist already | |
f9e03221 | 50 | user = register_user(request, register_form) |
0bc03620 | 51 | |
f9e03221 | 52 | if user: |
dce5c9cb CAW |
53 | # redirect the user to their homepage... there will be a |
54 | # message waiting for them to verify their email | |
0bc03620 CAW |
55 | return redirect( |
56 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 57 | user=user.username) |
24181820 | 58 | |
9038c9f9 CAW |
59 | return render_to_response( |
60 | request, | |
c9c24934 E |
61 | 'mediagoblin/auth/register.html', |
62 | {'register_form': register_form}) | |
24181820 CAW |
63 | |
64 | ||
692fd1c9 | 65 | def login(request): |
a3776717 | 66 | """ |
8e1e744d | 67 | MediaGoblin login view. |
a3776717 CAW |
68 | |
69 | If you provide the POST with 'next', it'll redirect to that view. | |
70 | """ | |
111a609d | 71 | login_form = auth_forms.LoginForm(request.form) |
692fd1c9 | 72 | |
a3776717 CAW |
73 | login_failed = False |
74 | ||
69b56235 | 75 | if request.method == 'POST': |
97aebda7 | 76 | |
b2c8dbcf J |
77 | username = login_form.data['username'] |
78 | ||
69b56235 | 79 | if login_form.validate(): |
75fc9368 | 80 | user = check_login_simple(username, login_form.password.data, True) |
b2c8dbcf | 81 | |
75fc9368 | 82 | if user: |
69b56235 SS |
83 | # set up login in session |
84 | request.session['user_id'] = unicode(user.id) | |
85 | request.session.save() | |
692fd1c9 | 86 | |
69b56235 SS |
87 | if request.form.get('next'): |
88 | return redirect(request, location=request.form['next']) | |
89 | else: | |
90 | return redirect(request, "index") | |
692fd1c9 | 91 | |
a3776717 | 92 | login_failed = True |
692fd1c9 | 93 | |
9038c9f9 CAW |
94 | return render_to_response( |
95 | request, | |
c9c24934 E |
96 | 'mediagoblin/auth/login.html', |
97 | {'login_form': login_form, | |
111a609d | 98 | 'next': request.GET.get('next') or request.form.get('next'), |
13bb1d67 RL |
99 | 'login_failed': login_failed, |
100 | 'allow_registration': mg_globals.app_config["allow_registration"]}) | |
692fd1c9 CAW |
101 | |
102 | ||
103 | def logout(request): | |
b97232fa CAW |
104 | # Maybe deleting the user_id parameter would be enough? |
105 | request.session.delete() | |
7b31a11c | 106 | |
9150244a | 107 | return redirect(request, "index") |
db1a438f | 108 | |
5866d1a8 | 109 | |
db1a438f | 110 | def verify_email(request): |
4c093e85 JW |
111 | """ |
112 | Email verification view | |
113 | ||
114 | validates GET parameters against database and unlocks the user account, if | |
115 | you are lucky :) | |
116 | """ | |
155f24f9 | 117 | # If we don't have userid and token parameters, we can't do anything; 404 |
285ffedd | 118 | if not 'userid' in request.GET or not 'token' in request.GET: |
de12b4e7 | 119 | return render_404(request) |
155f24f9 | 120 | |
70f8b2d0 | 121 | user = User.query.filter_by(id=request.args['userid']).first() |
db1a438f | 122 | |
00bb9550 | 123 | if user and user.verification_key == unicode(request.GET['token']): |
7a3d00ec | 124 | user.status = u'active' |
4facc7a0 | 125 | user.email_verified = True |
00bb9550 | 126 | user.verification_key = None |
daf02964 | 127 | |
db1a438f | 128 | user.save() |
daf02964 | 129 | |
fe80cb06 | 130 | messages.add_message( |
7b31a11c CAW |
131 | request, |
132 | messages.SUCCESS, | |
4b1adc13 CAW |
133 | _("Your email address has been verified. " |
134 | "You may now login, edit your profile, and submit images!")) | |
db1a438f | 135 | else: |
4b1adc13 CAW |
136 | messages.add_message( |
137 | request, | |
138 | messages.ERROR, | |
139 | _('The verification key or user id is incorrect')) | |
7b31a11c | 140 | |
269943a6 CAW |
141 | return redirect( |
142 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 143 | user=user.username) |
28afb47c | 144 | |
5866d1a8 | 145 | |
b93a6a22 AM |
146 | def resend_activation(request): |
147 | """ | |
148 | The reactivation view | |
149 | ||
150 | Resend the activation email. | |
151 | """ | |
84a7e770 | 152 | |
2fe69916 | 153 | if request.user is None: |
7903a14f AW |
154 | messages.add_message( |
155 | request, | |
156 | messages.ERROR, | |
2fe69916 | 157 | _('You must be logged in so we know who to send the email to!')) |
dfa6994d | 158 | |
5dbeda8a | 159 | return redirect(request, 'mediagoblin.auth.login') |
7903a14f | 160 | |
0ab21f98 | 161 | if request.user.email_verified: |
84a7e770 AW |
162 | messages.add_message( |
163 | request, | |
164 | messages.ERROR, | |
2fe69916 | 165 | _("You've already verified your email address!")) |
dfa6994d | 166 | |
2fe69916 | 167 | return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username']) |
84a7e770 | 168 | |
00bb9550 | 169 | request.user.verification_key = unicode(uuid.uuid4()) |
a77d952a | 170 | request.user.save() |
dfa6994d | 171 | |
bf33272f | 172 | email_debug_message(request) |
02d80437 | 173 | send_verification_email(request.user, request) |
b93a6a22 | 174 | |
61927e6e CAW |
175 | messages.add_message( |
176 | request, | |
177 | messages.INFO, | |
4b1adc13 | 178 | _('Resent your verification email.')) |
61927e6e CAW |
179 | return redirect( |
180 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 181 | user=request.user.username) |
25ba955e AV |
182 | |
183 | ||
184 | def forgot_password(request): | |
185 | """ | |
186 | Forgot password view | |
187 | ||
a89df961 SS |
188 | Sends an email with an url to renew forgotten password. |
189 | Use GET querystring parameter 'username' to pre-populate the input field | |
25ba955e | 190 | """ |
111a609d | 191 | fp_form = auth_forms.ForgotPassForm(request.form, |
a89df961 SS |
192 | username=request.args.get('username')) |
193 | ||
194 | if not (request.method == 'POST' and fp_form.validate()): | |
195 | # Either GET request, or invalid form submitted. Display the template | |
196 | return render_to_response(request, | |
197 | 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form}) | |
198 | ||
199 | # If we are here: method == POST and form is valid. username casing | |
200 | # has been sanitized. Store if a user was found by email. We should | |
201 | # not reveal if the operation was successful then as we don't want to | |
202 | # leak if an email address exists in the system. | |
9d140cb8 | 203 | found_by_email = '@' in fp_form.username.data |
a89df961 SS |
204 | |
205 | if found_by_email: | |
206 | user = User.query.filter_by( | |
9d140cb8 | 207 | email = fp_form.username.data).first() |
a89df961 SS |
208 | # Don't reveal success in case the lookup happened by email address. |
209 | success_message=_("If that email address (case sensitive!) is " | |
210 | "registered an email has been sent with instructions " | |
211 | "on how to change your password.") | |
212 | ||
213 | else: # found by username | |
214 | user = User.query.filter_by( | |
9d140cb8 | 215 | username = fp_form.username.data).first() |
a89df961 SS |
216 | |
217 | if user is None: | |
218 | messages.add_message(request, | |
219 | messages.WARNING, | |
220 | _("Couldn't find someone with that username.")) | |
221 | return redirect(request, 'mediagoblin.auth.forgot_password') | |
25ba955e | 222 | |
a89df961 SS |
223 | success_message=_("An email has been sent with instructions " |
224 | "on how to change your password.") | |
bf33272f | 225 | |
a89df961 SS |
226 | if user and not(user.email_verified and user.status == 'active'): |
227 | # Don't send reminder because user is inactive or has no verified email | |
228 | messages.add_message(request, | |
229 | messages.WARNING, | |
230 | _("Could not send password recovery email as your username is in" | |
231 | "active or your account's email address has not been verified.")) | |
25ba955e | 232 | |
a89df961 SS |
233 | return redirect(request, 'mediagoblin.user_pages.user_home', |
234 | user=user.username) | |
a85a2110 | 235 | |
a89df961 SS |
236 | # SUCCESS. Send reminder and return to login page |
237 | if user: | |
238 | user.fp_verification_key = unicode(uuid.uuid4()) | |
239 | user.fp_token_expire = datetime.datetime.now() + \ | |
240 | datetime.timedelta(days=10) | |
241 | user.save() | |
992e4f80 | 242 | |
a89df961 SS |
243 | email_debug_message(request) |
244 | send_fp_verification_email(user, request) | |
992e4f80 | 245 | |
a89df961 SS |
246 | messages.add_message(request, messages.INFO, success_message) |
247 | return redirect(request, 'mediagoblin.auth.login') | |
25ba955e AV |
248 | |
249 | ||
250 | def verify_forgot_password(request): | |
961fe381 CAW |
251 | """ |
252 | Check the forgot-password verification and possibly let the user | |
253 | change their password because of it. | |
254 | """ | |
f7ab6670 CAW |
255 | # get form data variables, and specifically check for presence of token |
256 | formdata = _process_for_token(request) | |
257 | if not formdata['has_userid_and_token']: | |
8d1c9863 CFD |
258 | return render_404(request) |
259 | ||
f7ab6670 CAW |
260 | formdata_token = formdata['vars']['token'] |
261 | formdata_userid = formdata['vars']['userid'] | |
262 | formdata_vars = formdata['vars'] | |
8d1c9863 | 263 | |
70f8b2d0 SS |
264 | # check if it's a valid user id |
265 | user = User.query.filter_by(id=formdata_userid).first() | |
266 | if not user: | |
8d1c9863 CFD |
267 | return render_404(request) |
268 | ||
269 | # check if we have a real user and correct token | |
dc39e455 E |
270 | if ((user and user.fp_verification_key and |
271 | user.fp_verification_key == unicode(formdata_token) and | |
2d540fed | 272 | datetime.datetime.now() < user.fp_token_expire |
7a3d00ec | 273 | and user.email_verified and user.status == 'active')): |
73fffbb8 | 274 | |
f7ab6670 | 275 | cp_form = auth_forms.ChangePassForm(formdata_vars) |
8d1c9863 CFD |
276 | |
277 | if request.method == 'POST' and cp_form.validate(): | |
9047b254 | 278 | user.pw_hash = auth_lib.bcrypt_gen_password_hash( |
9d140cb8 | 279 | cp_form.password.data) |
dc39e455 | 280 | user.fp_verification_key = None |
2d540fed | 281 | user.fp_token_expire = None |
8d1c9863 CFD |
282 | user.save() |
283 | ||
35149b11 JS |
284 | messages.add_message( |
285 | request, | |
286 | messages.INFO, | |
287 | _("You can now log in using your new password.")) | |
288 | return redirect(request, 'mediagoblin.auth.login') | |
25ba955e | 289 | else: |
73fffbb8 CAW |
290 | return render_to_response( |
291 | request, | |
292 | 'mediagoblin/auth/change_fp.html', | |
293 | {'cp_form': cp_form}) | |
294 | ||
70f8b2d0 | 295 | # in case there is a valid id but no user with that id in the db |
8d1c9863 CFD |
296 | # or the token expired |
297 | else: | |
298 | return render_404(request) | |
299 | ||
300 | ||
301 | def _process_for_token(request): | |
302 | """ | |
f7ab6670 | 303 | Checks for tokens in formdata without prior knowledge of request method |
8d1c9863 | 304 | |
f7ab6670 CAW |
305 | For now, returns whether the userid and token formdata variables exist, and |
306 | the formdata variables in a hash. Perhaps an object is warranted? | |
8d1c9863 | 307 | """ |
f7ab6670 | 308 | # retrieve the formdata variables |
8d1c9863 | 309 | if request.method == 'GET': |
f7ab6670 | 310 | formdata_vars = request.GET |
8d1c9863 | 311 | else: |
111a609d | 312 | formdata_vars = request.form |
8d1c9863 | 313 | |
f7ab6670 CAW |
314 | formdata = { |
315 | 'vars': formdata_vars, | |
2c9e8184 | 316 | 'has_userid_and_token': |
285ffedd | 317 | 'userid' in formdata_vars and 'token' in formdata_vars} |
2c9e8184 | 318 | |
f7ab6670 | 319 | return formdata |