Better media_data sql table for images.
[mediagoblin.git] / mediagoblin / auth / views.py
CommitLineData
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 17import uuid
25ba955e 18import datetime
a77d952a 19
1c63ad5d 20from webob import exc
24181820 21
cfe46f3e 22from mediagoblin import messages
13677ef9 23from mediagoblin import mg_globals
152a3bfa 24from mediagoblin.tools.response import render_to_response, redirect, render_404
ae3bc7fa 25from mediagoblin.tools.translate import pass_to_ugettext as _
25ba955e 26from mediagoblin.db.util import ObjectId, InvalidId
24181820
CAW
27from mediagoblin.auth import lib as auth_lib
28from mediagoblin.auth import forms as auth_forms
25ba955e
AV
29from mediagoblin.auth.lib import send_verification_email, \
30 send_fp_verification_email
24181820
CAW
31
32
bf33272f
E
33def 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
46def 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 112def 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
151def 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 158def 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
195def 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
233def 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
293def 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
346def _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