In this commit, I have made a few changes and tightened up some of my models
[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
70f8b2d0 20from mediagoblin import messages, mg_globals
b0c8328e 21from mediagoblin.db.models import User
152a3bfa 22from mediagoblin.tools.response import render_to_response, redirect, render_404
a789b713 23from mediagoblin.tools.translate import pass_to_ugettext as _
02b6892c 24from mediagoblin.tools.mail import email_debug_message
24181820
CAW
25from mediagoblin.auth import lib as auth_lib
26from mediagoblin.auth import forms as auth_forms
97aebda7 27from mediagoblin.auth.lib import send_fp_verification_email
f9e03221 28from mediagoblin.auth.tools import (send_verification_email, register_user,
75fc9368 29 check_login_simple)
24181820 30
bf33272f 31
24181820 32def 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 65def 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
103def 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 110def 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
146def 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
184def 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
250def 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
301def _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