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 | |
1c63ad5d | 20 | from webob import exc |
24181820 | 21 | |
cfe46f3e | 22 | from mediagoblin import messages |
13677ef9 | 23 | from mediagoblin import mg_globals |
152a3bfa | 24 | from mediagoblin.tools.response import render_to_response, redirect, render_404 |
ae3bc7fa | 25 | from mediagoblin.tools.translate import pass_to_ugettext as _ |
25ba955e | 26 | from mediagoblin.db.util import ObjectId, InvalidId |
24181820 CAW |
27 | from mediagoblin.auth import lib as auth_lib |
28 | from mediagoblin.auth import forms as auth_forms | |
25ba955e AV |
29 | from mediagoblin.auth.lib import send_verification_email, \ |
30 | send_fp_verification_email | |
24181820 CAW |
31 | |
32 | ||
bf33272f E |
33 | def email_debug_message(request): |
34 | """ | |
35 | If the server is running in email debug mode (which is | |
36 | the current default), give a debug message to the user | |
37 | so that they have an idea where to find their email. | |
38 | """ | |
39 | if mg_globals.app_config['email_debug_mode']: | |
40 | # DEBUG message, no need to translate | |
41 | messages.add_message(request, messages.DEBUG, | |
42 | u"This instance is running in email debug mode. " | |
43 | u"The email will be on the console of the server process.") | |
44 | ||
45 | ||
24181820 CAW |
46 | def register(request): |
47 | """ | |
48 | Your classic registration view! | |
49 | """ | |
13677ef9 RL |
50 | # Redirects to indexpage if registrations are disabled |
51 | if not mg_globals.app_config["allow_registration"]: | |
166dc91a CAW |
52 | messages.add_message( |
53 | request, | |
54 | messages.WARNING, | |
4b1adc13 | 55 | _('Sorry, registration is disabled on this instance.')) |
13677ef9 RL |
56 | return redirect(request, "index") |
57 | ||
24181820 CAW |
58 | register_form = auth_forms.RegistrationForm(request.POST) |
59 | ||
60 | if request.method == 'POST' and register_form.validate(): | |
61 | # TODO: Make sure the user doesn't exist already | |
08750772 | 62 | username = unicode(request.POST['username'].lower()) |
53280164 E |
63 | em_user, em_dom = unicode(request.POST['email']).split("@", 1) |
64 | em_dom = em_dom.lower() | |
65 | email = em_user + "@" + em_dom | |
dc49cf60 | 66 | users_with_username = request.db.User.find( |
08750772 | 67 | {'username': username}).count() |
0bf099d7 | 68 | users_with_email = request.db.User.find( |
08750772 | 69 | {'email': email}).count() |
24181820 | 70 | |
9f6ea475 CAW |
71 | extra_validation_passes = True |
72 | ||
24181820 CAW |
73 | if users_with_username: |
74 | register_form.username.errors.append( | |
4b1adc13 | 75 | _(u'Sorry, a user with that name already exists.')) |
9f6ea475 CAW |
76 | extra_validation_passes = False |
77 | if users_with_email: | |
0bf099d7 | 78 | register_form.email.errors.append( |
5ab3855e | 79 | _(u'Sorry, a user with that email address already exists.')) |
9f6ea475 | 80 | extra_validation_passes = False |
24181820 | 81 | |
9f6ea475 | 82 | if extra_validation_passes: |
24181820 | 83 | # Create the user |
0bc03620 | 84 | user = request.db.User() |
5a4e3ff1 | 85 | user.username = username |
809cbfc5 | 86 | user.email = email |
9047b254 | 87 | user.pw_hash = auth_lib.bcrypt_gen_password_hash( |
24181820 | 88 | request.POST['password']) |
479e8a83 | 89 | user.verification_key = unicode(uuid.uuid4()) |
0bc03620 CAW |
90 | user.save(validate=True) |
91 | ||
f73f4c4b | 92 | # log the user in |
eabe6b67 | 93 | request.session['user_id'] = unicode(user._id) |
f73f4c4b CAW |
94 | request.session.save() |
95 | ||
96 | # send verification email | |
bf33272f | 97 | email_debug_message(request) |
0bc03620 CAW |
98 | send_verification_email(user, request) |
99 | ||
dce5c9cb CAW |
100 | # redirect the user to their homepage... there will be a |
101 | # message waiting for them to verify their email | |
0bc03620 CAW |
102 | return redirect( |
103 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 104 | user=user.username) |
24181820 | 105 | |
9038c9f9 CAW |
106 | return render_to_response( |
107 | request, | |
c9c24934 E |
108 | 'mediagoblin/auth/register.html', |
109 | {'register_form': register_form}) | |
24181820 CAW |
110 | |
111 | ||
692fd1c9 | 112 | def login(request): |
a3776717 | 113 | """ |
8e1e744d | 114 | MediaGoblin login view. |
a3776717 CAW |
115 | |
116 | If you provide the POST with 'next', it'll redirect to that view. | |
117 | """ | |
692fd1c9 CAW |
118 | login_form = auth_forms.LoginForm(request.POST) |
119 | ||
a3776717 CAW |
120 | login_failed = False |
121 | ||
692fd1c9 | 122 | if request.method == 'POST' and login_form.validate(): |
b058cf15 | 123 | user = request.db.User.one( |
ce72a1bb | 124 | {'username': request.POST['username'].lower()}) |
692fd1c9 | 125 | |
d1938963 | 126 | if user and user.check_login(request.POST['password']): |
692fd1c9 | 127 | # set up login in session |
eabe6b67 | 128 | request.session['user_id'] = unicode(user._id) |
a3776717 | 129 | request.session.save() |
692fd1c9 | 130 | |
574d1511 | 131 | if request.POST.get('next'): |
a3776717 CAW |
132 | return exc.HTTPFound(location=request.POST['next']) |
133 | else: | |
9150244a | 134 | return redirect(request, "index") |
692fd1c9 CAW |
135 | |
136 | else: | |
137 | # Prevent detecting who's on this system by testing login | |
138 | # attempt timings | |
139 | auth_lib.fake_login_attempt() | |
a3776717 | 140 | login_failed = True |
692fd1c9 | 141 | |
9038c9f9 CAW |
142 | return render_to_response( |
143 | request, | |
c9c24934 E |
144 | 'mediagoblin/auth/login.html', |
145 | {'login_form': login_form, | |
146 | 'next': request.GET.get('next') or request.POST.get('next'), | |
13bb1d67 RL |
147 | 'login_failed': login_failed, |
148 | 'allow_registration': mg_globals.app_config["allow_registration"]}) | |
692fd1c9 CAW |
149 | |
150 | ||
151 | def logout(request): | |
b97232fa CAW |
152 | # Maybe deleting the user_id parameter would be enough? |
153 | request.session.delete() | |
7b31a11c | 154 | |
9150244a | 155 | return redirect(request, "index") |
db1a438f | 156 | |
5866d1a8 | 157 | |
db1a438f | 158 | def verify_email(request): |
4c093e85 JW |
159 | """ |
160 | Email verification view | |
161 | ||
162 | validates GET parameters against database and unlocks the user account, if | |
163 | you are lucky :) | |
164 | """ | |
155f24f9 | 165 | # If we don't have userid and token parameters, we can't do anything; 404 |
285ffedd | 166 | if not 'userid' in request.GET or not 'token' in request.GET: |
de12b4e7 | 167 | return render_404(request) |
155f24f9 | 168 | |
db1a438f | 169 | user = request.db.User.find_one( |
e0f84870 | 170 | {'_id': ObjectId(unicode(request.GET['userid']))}) |
db1a438f | 171 | |
00bb9550 | 172 | if user and user.verification_key == unicode(request.GET['token']): |
7a3d00ec | 173 | user.status = u'active' |
4facc7a0 | 174 | user.email_verified = True |
00bb9550 | 175 | user.verification_key = None |
daf02964 | 176 | |
db1a438f | 177 | user.save() |
daf02964 | 178 | |
fe80cb06 | 179 | messages.add_message( |
7b31a11c CAW |
180 | request, |
181 | messages.SUCCESS, | |
4b1adc13 CAW |
182 | _("Your email address has been verified. " |
183 | "You may now login, edit your profile, and submit images!")) | |
db1a438f | 184 | else: |
4b1adc13 CAW |
185 | messages.add_message( |
186 | request, | |
187 | messages.ERROR, | |
188 | _('The verification key or user id is incorrect')) | |
7b31a11c | 189 | |
269943a6 CAW |
190 | return redirect( |
191 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 192 | user=user.username) |
28afb47c | 193 | |
5866d1a8 | 194 | |
b93a6a22 AM |
195 | def resend_activation(request): |
196 | """ | |
197 | The reactivation view | |
198 | ||
199 | Resend the activation email. | |
200 | """ | |
84a7e770 | 201 | |
2fe69916 | 202 | if request.user is None: |
7903a14f AW |
203 | messages.add_message( |
204 | request, | |
205 | messages.ERROR, | |
2fe69916 | 206 | _('You must be logged in so we know who to send the email to!')) |
7903a14f | 207 | |
5dbeda8a | 208 | return redirect(request, 'mediagoblin.auth.login') |
7903a14f | 209 | |
0ab21f98 | 210 | if request.user.email_verified: |
84a7e770 AW |
211 | messages.add_message( |
212 | request, | |
213 | messages.ERROR, | |
2fe69916 | 214 | _("You've already verified your email address!")) |
84a7e770 | 215 | |
2fe69916 | 216 | return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username']) |
84a7e770 | 217 | |
00bb9550 | 218 | request.user.verification_key = unicode(uuid.uuid4()) |
a77d952a | 219 | request.user.save() |
84a7e770 | 220 | |
bf33272f | 221 | email_debug_message(request) |
02d80437 | 222 | send_verification_email(request.user, request) |
b93a6a22 | 223 | |
61927e6e CAW |
224 | messages.add_message( |
225 | request, | |
226 | messages.INFO, | |
4b1adc13 | 227 | _('Resent your verification email.')) |
61927e6e CAW |
228 | return redirect( |
229 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 230 | user=request.user.username) |
25ba955e AV |
231 | |
232 | ||
233 | def forgot_password(request): | |
234 | """ | |
235 | Forgot password view | |
236 | ||
992e4f80 | 237 | Sends an email with an url to renew forgotten password |
25ba955e AV |
238 | """ |
239 | fp_form = auth_forms.ForgotPassForm(request.POST) | |
240 | ||
241 | if request.method == 'POST' and fp_form.validate(): | |
bf33272f | 242 | |
d1a64326 CAW |
243 | # '$or' not available till mongodb 1.5.3 |
244 | user = request.db.User.find_one( | |
245 | {'username': request.POST['username']}) | |
246 | if not user: | |
247 | user = request.db.User.find_one( | |
248 | {'email': request.POST['username']}) | |
25ba955e | 249 | |
24966c43 | 250 | if user: |
7a3d00ec | 251 | if user.email_verified and user.status == 'active': |
dc39e455 | 252 | user.fp_verification_key = unicode(uuid.uuid4()) |
2d540fed | 253 | user.fp_token_expire = datetime.datetime.now() + \ |
a85a2110 CAW |
254 | datetime.timedelta(days=10) |
255 | user.save() | |
256 | ||
257 | send_fp_verification_email(user, request) | |
992e4f80 JS |
258 | |
259 | messages.add_message( | |
260 | request, | |
261 | messages.INFO, | |
262 | _("An email has been sent with instructions on how to " | |
263 | "change your password.")) | |
264 | email_debug_message(request) | |
265 | ||
a85a2110 CAW |
266 | else: |
267 | # special case... we can't send the email because the | |
268 | # username is inactive / hasn't verified their email | |
269 | messages.add_message( | |
270 | request, | |
271 | messages.WARNING, | |
272 | _("Could not send password recovery email as " | |
273 | "your username is inactive or your account's " | |
274 | "email address has not been verified.")) | |
275 | ||
276 | return redirect( | |
277 | request, 'mediagoblin.user_pages.user_home', | |
5a4e3ff1 | 278 | user=user.username) |
992e4f80 JS |
279 | return redirect(request, 'mediagoblin.auth.login') |
280 | else: | |
281 | messages.add_message( | |
282 | request, | |
283 | messages.WARNING, | |
284 | _("Couldn't find someone with that username or email.")) | |
285 | return redirect(request, 'mediagoblin.auth.forgot_password') | |
25ba955e AV |
286 | |
287 | return render_to_response( | |
2c9e8184 CAW |
288 | request, |
289 | 'mediagoblin/auth/forgot_password.html', | |
290 | {'fp_form': fp_form}) | |
25ba955e AV |
291 | |
292 | ||
293 | def verify_forgot_password(request): | |
961fe381 CAW |
294 | """ |
295 | Check the forgot-password verification and possibly let the user | |
296 | change their password because of it. | |
297 | """ | |
f7ab6670 CAW |
298 | # get form data variables, and specifically check for presence of token |
299 | formdata = _process_for_token(request) | |
300 | if not formdata['has_userid_and_token']: | |
8d1c9863 CFD |
301 | return render_404(request) |
302 | ||
f7ab6670 CAW |
303 | formdata_token = formdata['vars']['token'] |
304 | formdata_userid = formdata['vars']['userid'] | |
305 | formdata_vars = formdata['vars'] | |
8d1c9863 CFD |
306 | |
307 | # check if it's a valid Id | |
308 | try: | |
309 | user = request.db.User.find_one( | |
f7ab6670 | 310 | {'_id': ObjectId(unicode(formdata_userid))}) |
8d1c9863 CFD |
311 | except InvalidId: |
312 | return render_404(request) | |
313 | ||
314 | # check if we have a real user and correct token | |
dc39e455 E |
315 | if ((user and user.fp_verification_key and |
316 | user.fp_verification_key == unicode(formdata_token) and | |
2d540fed | 317 | datetime.datetime.now() < user.fp_token_expire |
7a3d00ec | 318 | and user.email_verified and user.status == 'active')): |
73fffbb8 | 319 | |
f7ab6670 | 320 | cp_form = auth_forms.ChangePassForm(formdata_vars) |
8d1c9863 CFD |
321 | |
322 | if request.method == 'POST' and cp_form.validate(): | |
9047b254 | 323 | user.pw_hash = auth_lib.bcrypt_gen_password_hash( |
8d1c9863 | 324 | request.POST['password']) |
dc39e455 | 325 | user.fp_verification_key = None |
2d540fed | 326 | user.fp_token_expire = None |
8d1c9863 CFD |
327 | user.save() |
328 | ||
35149b11 JS |
329 | messages.add_message( |
330 | request, | |
331 | messages.INFO, | |
332 | _("You can now log in using your new password.")) | |
333 | return redirect(request, 'mediagoblin.auth.login') | |
25ba955e | 334 | else: |
73fffbb8 CAW |
335 | return render_to_response( |
336 | request, | |
337 | 'mediagoblin/auth/change_fp.html', | |
338 | {'cp_form': cp_form}) | |
339 | ||
8d1c9863 CFD |
340 | # in case there is a valid id but no user whit that id in the db |
341 | # or the token expired | |
342 | else: | |
343 | return render_404(request) | |
344 | ||
345 | ||
346 | def _process_for_token(request): | |
347 | """ | |
f7ab6670 | 348 | Checks for tokens in formdata without prior knowledge of request method |
8d1c9863 | 349 | |
f7ab6670 CAW |
350 | For now, returns whether the userid and token formdata variables exist, and |
351 | the formdata variables in a hash. Perhaps an object is warranted? | |
8d1c9863 | 352 | """ |
f7ab6670 | 353 | # retrieve the formdata variables |
8d1c9863 | 354 | if request.method == 'GET': |
f7ab6670 | 355 | formdata_vars = request.GET |
8d1c9863 | 356 | else: |
f7ab6670 | 357 | formdata_vars = request.POST |
8d1c9863 | 358 | |
f7ab6670 CAW |
359 | formdata = { |
360 | 'vars': formdata_vars, | |
2c9e8184 | 361 | 'has_userid_and_token': |
285ffedd | 362 | 'userid' in formdata_vars and 'token' in formdata_vars} |
2c9e8184 | 363 | |
f7ab6670 | 364 | return formdata |