Merge remote branch 'remotes/nyergler/569-application-middleware'
[mediagoblin.git] / mediagoblin / auth / views.py
CommitLineData
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 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
de12b4e7 24from mediagoblin.util import render_to_response, redirect, render_404
4b1adc13 25from mediagoblin.util 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
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 109def 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
148def 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 155def 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
192def 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
213def 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
266def 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
315def _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