Commit | Line | Data |
---|---|---|
8e1e744d | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
12a100e4 | 2 | # Copyright (C) 2011 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 |
de12b4e7 | 24 | from mediagoblin.util import render_to_response, redirect, render_404 |
4b1adc13 | 25 | from mediagoblin.util 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 MH |
62 | username = unicode(request.POST['username'].lower()) |
63 | email = unicode(request.POST['email'].lower()) | |
dc49cf60 | 64 | users_with_username = request.db.User.find( |
08750772 | 65 | {'username': username}).count() |
0bf099d7 | 66 | users_with_email = request.db.User.find( |
08750772 | 67 | {'email': email}).count() |
24181820 | 68 | |
9f6ea475 CAW |
69 | extra_validation_passes = True |
70 | ||
24181820 CAW |
71 | if users_with_username: |
72 | register_form.username.errors.append( | |
4b1adc13 | 73 | _(u'Sorry, a user with that name already exists.')) |
9f6ea475 CAW |
74 | extra_validation_passes = False |
75 | if users_with_email: | |
0bf099d7 AV |
76 | register_form.email.errors.append( |
77 | _(u'Sorry, that email address has already been taken.')) | |
9f6ea475 | 78 | extra_validation_passes = False |
24181820 | 79 | |
9f6ea475 | 80 | if extra_validation_passes: |
24181820 | 81 | # Create the user |
0bc03620 | 82 | user = request.db.User() |
08750772 MH |
83 | user['username'] = username |
84 | user['email'] = email | |
0bc03620 | 85 | user['pw_hash'] = auth_lib.bcrypt_gen_password_hash( |
24181820 | 86 | request.POST['password']) |
0bc03620 CAW |
87 | user.save(validate=True) |
88 | ||
f73f4c4b CAW |
89 | # log the user in |
90 | request.session['user_id'] = unicode(user['_id']) | |
91 | request.session.save() | |
92 | ||
93 | # send verification email | |
bf33272f | 94 | email_debug_message(request) |
0bc03620 CAW |
95 | send_verification_email(user, request) |
96 | ||
dce5c9cb CAW |
97 | # redirect the user to their homepage... there will be a |
98 | # message waiting for them to verify their email | |
0bc03620 CAW |
99 | return redirect( |
100 | request, 'mediagoblin.user_pages.user_home', | |
101 | user=user['username']) | |
24181820 | 102 | |
9038c9f9 CAW |
103 | return render_to_response( |
104 | request, | |
c9c24934 E |
105 | 'mediagoblin/auth/register.html', |
106 | {'register_form': register_form}) | |
24181820 CAW |
107 | |
108 | ||
692fd1c9 | 109 | def login(request): |
a3776717 | 110 | """ |
8e1e744d | 111 | MediaGoblin login view. |
a3776717 CAW |
112 | |
113 | If you provide the POST with 'next', it'll redirect to that view. | |
114 | """ | |
692fd1c9 CAW |
115 | login_form = auth_forms.LoginForm(request.POST) |
116 | ||
a3776717 CAW |
117 | login_failed = False |
118 | ||
692fd1c9 | 119 | if request.method == 'POST' and login_form.validate(): |
b058cf15 | 120 | user = request.db.User.one( |
ce72a1bb | 121 | {'username': request.POST['username'].lower()}) |
692fd1c9 | 122 | |
d1938963 | 123 | if user and user.check_login(request.POST['password']): |
692fd1c9 CAW |
124 | # set up login in session |
125 | request.session['user_id'] = unicode(user['_id']) | |
a3776717 | 126 | request.session.save() |
692fd1c9 | 127 | |
574d1511 | 128 | if request.POST.get('next'): |
a3776717 CAW |
129 | return exc.HTTPFound(location=request.POST['next']) |
130 | else: | |
9150244a | 131 | return redirect(request, "index") |
692fd1c9 CAW |
132 | |
133 | else: | |
134 | # Prevent detecting who's on this system by testing login | |
135 | # attempt timings | |
136 | auth_lib.fake_login_attempt() | |
a3776717 | 137 | login_failed = True |
692fd1c9 | 138 | |
9038c9f9 CAW |
139 | return render_to_response( |
140 | request, | |
c9c24934 E |
141 | 'mediagoblin/auth/login.html', |
142 | {'login_form': login_form, | |
143 | 'next': request.GET.get('next') or request.POST.get('next'), | |
13bb1d67 RL |
144 | 'login_failed': login_failed, |
145 | 'allow_registration': mg_globals.app_config["allow_registration"]}) | |
692fd1c9 CAW |
146 | |
147 | ||
148 | def logout(request): | |
b97232fa CAW |
149 | # Maybe deleting the user_id parameter would be enough? |
150 | request.session.delete() | |
7b31a11c | 151 | |
9150244a | 152 | return redirect(request, "index") |
db1a438f | 153 | |
5866d1a8 | 154 | |
db1a438f | 155 | def verify_email(request): |
4c093e85 JW |
156 | """ |
157 | Email verification view | |
158 | ||
159 | validates GET parameters against database and unlocks the user account, if | |
160 | you are lucky :) | |
161 | """ | |
155f24f9 CAW |
162 | # If we don't have userid and token parameters, we can't do anything; 404 |
163 | if not request.GET.has_key('userid') or not request.GET.has_key('token'): | |
de12b4e7 | 164 | return render_404(request) |
155f24f9 | 165 | |
db1a438f | 166 | user = request.db.User.find_one( |
e0f84870 | 167 | {'_id': ObjectId(unicode(request.GET['userid']))}) |
db1a438f | 168 | |
155f24f9 | 169 | if user and user['verification_key'] == unicode(request.GET['token']): |
4185e644 CAW |
170 | user[u'status'] = u'active' |
171 | user[u'email_verified'] = True | |
daf02964 CAW |
172 | user[u'verification_key'] = None |
173 | ||
db1a438f | 174 | user.save() |
daf02964 | 175 | |
fe80cb06 | 176 | messages.add_message( |
7b31a11c CAW |
177 | request, |
178 | messages.SUCCESS, | |
4b1adc13 CAW |
179 | _("Your email address has been verified. " |
180 | "You may now login, edit your profile, and submit images!")) | |
db1a438f | 181 | else: |
4b1adc13 CAW |
182 | messages.add_message( |
183 | request, | |
184 | messages.ERROR, | |
185 | _('The verification key or user id is incorrect')) | |
7b31a11c | 186 | |
269943a6 CAW |
187 | return redirect( |
188 | request, 'mediagoblin.user_pages.user_home', | |
788272f3 | 189 | user=user['username']) |
28afb47c | 190 | |
5866d1a8 | 191 | |
b93a6a22 AM |
192 | def resend_activation(request): |
193 | """ | |
194 | The reactivation view | |
195 | ||
196 | Resend the activation email. | |
197 | """ | |
4185e644 | 198 | request.user[u'verification_key'] = unicode(uuid.uuid4()) |
a77d952a | 199 | request.user.save() |
b93a6a22 | 200 | |
bf33272f | 201 | email_debug_message(request) |
02d80437 | 202 | send_verification_email(request.user, request) |
b93a6a22 | 203 | |
61927e6e CAW |
204 | messages.add_message( |
205 | request, | |
206 | messages.INFO, | |
4b1adc13 | 207 | _('Resent your verification email.')) |
61927e6e CAW |
208 | return redirect( |
209 | request, 'mediagoblin.user_pages.user_home', | |
210 | user=request.user['username']) | |
25ba955e AV |
211 | |
212 | ||
213 | def forgot_password(request): | |
214 | """ | |
215 | Forgot password view | |
216 | ||
217 | Sends an email whit an url to renew forgoten password | |
218 | """ | |
219 | fp_form = auth_forms.ForgotPassForm(request.POST) | |
220 | ||
221 | if request.method == 'POST' and fp_form.validate(): | |
bf33272f E |
222 | |
223 | # Here, so it doesn't depend on the actual mail being sent | |
224 | # and thus doesn't reveal, wether mail was sent. | |
225 | email_debug_message(request) | |
226 | ||
d1a64326 CAW |
227 | # '$or' not available till mongodb 1.5.3 |
228 | user = request.db.User.find_one( | |
229 | {'username': request.POST['username']}) | |
230 | if not user: | |
231 | user = request.db.User.find_one( | |
232 | {'email': request.POST['username']}) | |
25ba955e | 233 | |
24966c43 | 234 | if user: |
a85a2110 | 235 | if user['email_verified'] and user['status'] == 'active': |
4185e644 CAW |
236 | user[u'fp_verification_key'] = unicode(uuid.uuid4()) |
237 | user[u'fp_token_expire'] = datetime.datetime.now() + \ | |
a85a2110 CAW |
238 | datetime.timedelta(days=10) |
239 | user.save() | |
240 | ||
241 | send_fp_verification_email(user, request) | |
242 | else: | |
243 | # special case... we can't send the email because the | |
244 | # username is inactive / hasn't verified their email | |
245 | messages.add_message( | |
246 | request, | |
247 | messages.WARNING, | |
248 | _("Could not send password recovery email as " | |
249 | "your username is inactive or your account's " | |
250 | "email address has not been verified.")) | |
251 | ||
252 | return redirect( | |
253 | request, 'mediagoblin.user_pages.user_home', | |
254 | user=user['username']) | |
25ba955e | 255 | |
25ba955e | 256 | |
24966c43 CFD |
257 | # do not reveal whether or not there is a matching user, just move along |
258 | return redirect(request, 'mediagoblin.auth.fp_email_sent') | |
25ba955e AV |
259 | |
260 | return render_to_response( | |
2c9e8184 CAW |
261 | request, |
262 | 'mediagoblin/auth/forgot_password.html', | |
263 | {'fp_form': fp_form}) | |
25ba955e AV |
264 | |
265 | ||
266 | def verify_forgot_password(request): | |
961fe381 CAW |
267 | """ |
268 | Check the forgot-password verification and possibly let the user | |
269 | change their password because of it. | |
270 | """ | |
f7ab6670 CAW |
271 | # get form data variables, and specifically check for presence of token |
272 | formdata = _process_for_token(request) | |
273 | if not formdata['has_userid_and_token']: | |
8d1c9863 CFD |
274 | return render_404(request) |
275 | ||
f7ab6670 CAW |
276 | formdata_token = formdata['vars']['token'] |
277 | formdata_userid = formdata['vars']['userid'] | |
278 | formdata_vars = formdata['vars'] | |
8d1c9863 CFD |
279 | |
280 | # check if it's a valid Id | |
281 | try: | |
282 | user = request.db.User.find_one( | |
f7ab6670 | 283 | {'_id': ObjectId(unicode(formdata_userid))}) |
8d1c9863 CFD |
284 | except InvalidId: |
285 | return render_404(request) | |
286 | ||
287 | # check if we have a real user and correct token | |
73fffbb8 | 288 | if ((user and user['fp_verification_key'] and |
f7ab6670 | 289 | user['fp_verification_key'] == unicode(formdata_token) and |
a85a2110 CAW |
290 | datetime.datetime.now() < user['fp_token_expire'] |
291 | and user['email_verified'] and user['status'] == 'active')): | |
73fffbb8 | 292 | |
f7ab6670 | 293 | cp_form = auth_forms.ChangePassForm(formdata_vars) |
8d1c9863 CFD |
294 | |
295 | if request.method == 'POST' and cp_form.validate(): | |
4185e644 | 296 | user[u'pw_hash'] = auth_lib.bcrypt_gen_password_hash( |
8d1c9863 | 297 | request.POST['password']) |
4185e644 CAW |
298 | user[u'fp_verification_key'] = None |
299 | user[u'fp_token_expire'] = None | |
8d1c9863 CFD |
300 | user.save() |
301 | ||
302 | return redirect(request, 'mediagoblin.auth.fp_changed_success') | |
25ba955e | 303 | else: |
73fffbb8 CAW |
304 | return render_to_response( |
305 | request, | |
306 | 'mediagoblin/auth/change_fp.html', | |
307 | {'cp_form': cp_form}) | |
308 | ||
8d1c9863 CFD |
309 | # in case there is a valid id but no user whit that id in the db |
310 | # or the token expired | |
311 | else: | |
312 | return render_404(request) | |
313 | ||
314 | ||
315 | def _process_for_token(request): | |
316 | """ | |
f7ab6670 | 317 | Checks for tokens in formdata without prior knowledge of request method |
8d1c9863 | 318 | |
f7ab6670 CAW |
319 | For now, returns whether the userid and token formdata variables exist, and |
320 | the formdata variables in a hash. Perhaps an object is warranted? | |
8d1c9863 | 321 | """ |
f7ab6670 | 322 | # retrieve the formdata variables |
8d1c9863 | 323 | if request.method == 'GET': |
f7ab6670 | 324 | formdata_vars = request.GET |
8d1c9863 | 325 | else: |
f7ab6670 | 326 | formdata_vars = request.POST |
8d1c9863 | 327 | |
f7ab6670 CAW |
328 | formdata = { |
329 | 'vars': formdata_vars, | |
2c9e8184 | 330 | 'has_userid_and_token': |
f7ab6670 | 331 | formdata_vars.has_key('userid') and formdata_vars.has_key('token')} |
2c9e8184 | 332 | |
f7ab6670 | 333 | return formdata |