Fix #5451 - add_message inconsistencies
[mediagoblin.git] / mediagoblin / plugins / basic_auth / views.py
CommitLineData
aeae6cc2
RE
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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/>.
16from itsdangerous import BadSignature
17
18from mediagoblin import messages
d88fcb03 19from mediagoblin.db.models import LocalUser
af665c4e 20from mediagoblin.decorators import require_active_login
aeae6cc2
RE
21from mediagoblin.plugins.basic_auth import forms, tools
22from mediagoblin.tools.crypto import get_timed_signer_url
23from mediagoblin.tools.mail import email_debug_message
24from mediagoblin.tools.response import redirect, render_to_response, render_404
25from mediagoblin.tools.translate import pass_to_ugettext as _
26
27
28def forgot_password(request):
29 """
30 Forgot password view
31
32 Sends an email with an url to renew forgotten password.
33 Use GET querystring parameter 'username' to pre-populate the input field
34 """
35 fp_form = forms.ForgotPassForm(request.form,
36 username=request.args.get('username'))
37
38 if not (request.method == 'POST' and fp_form.validate()):
39 # Either GET request, or invalid form submitted. Display the template
40 return render_to_response(request,
41 'mediagoblin/plugins/basic_auth/forgot_password.html',
42 {'fp_form': fp_form})
43
44 # If we are here: method == POST and form is valid. username casing
45 # has been sanitized. Store if a user was found by email. We should
46 # not reveal if the operation was successful then as we don't want to
47 # leak if an email address exists in the system.
48 found_by_email = '@' in fp_form.username.data
49
50 if found_by_email:
d88fcb03 51 user = LocalUser.query.filter_by(
aeae6cc2
RE
52 email=fp_form.username.data).first()
53 # Don't reveal success in case the lookup happened by email address.
54 success_message = _("If that email address (case sensitive!) is "
55 "registered an email has been sent with "
56 "instructions on how to change your password.")
57
58 else: # found by username
d88fcb03 59 user = LocalUser.query.filter_by(
aeae6cc2
RE
60 username=fp_form.username.data).first()
61
62 if user is None:
5c7b2a63
AB
63 messages.add_message(
64 request,
65 messages.WARNING,
66 _("Couldn't find someone with that username."))
64c035b3
CAW
67 return redirect(request,
68 'mediagoblin.plugins.basic_auth.forgot_password')
aeae6cc2
RE
69
70 success_message = _("An email has been sent with instructions "
71 "on how to change your password.")
72
6180e3a9 73 if user and user.has_privilege(u'active') is False:
aeae6cc2 74 # Don't send reminder because user is inactive or has no verified email
5c7b2a63
AB
75 messages.add_message(
76 request,
aeae6cc2 77 messages.WARNING,
5c7b2a63
AB
78 _("Could not send password recovery email as your username is "
79 "inactive or your account's email address has not been verified."))
aeae6cc2
RE
80
81 return redirect(request, 'mediagoblin.user_pages.user_home',
82 user=user.username)
83
84 # SUCCESS. Send reminder and return to login page
85 if user:
86 email_debug_message(request)
87 tools.send_fp_verification_email(user, request)
88
89 messages.add_message(request, messages.INFO, success_message)
90 return redirect(request, 'mediagoblin.auth.login')
91
92
93def verify_forgot_password(request):
94 """
95 Check the forgot-password verification and possibly let the user
96 change their password because of it.
97 """
98 # get form data variables, and specifically check for presence of token
99 formdata = _process_for_token(request)
100 if not formdata['has_token']:
101 return render_404(request)
102
103 formdata_vars = formdata['vars']
104
105 # Catch error if token is faked or expired
106 try:
107 token = get_timed_signer_url("mail_verification_token") \
108 .loads(formdata_vars['token'], max_age=10*24*3600)
109 except BadSignature:
110 messages.add_message(
111 request,
112 messages.ERROR,
113 _('The verification key or user id is incorrect.'))
114
115 return redirect(
116 request,
117 'index')
118
119 # check if it's a valid user id
d88fcb03 120 user = LocalUser.query.filter_by(id=int(token)).first()
aeae6cc2
RE
121
122 # no user in db
123 if not user:
124 messages.add_message(
125 request, messages.ERROR,
126 _('The user id is incorrect.'))
127 return redirect(
128 request, 'index')
129
130 # check if user active and has email verified
6180e3a9 131 if user.has_privilege(u'active'):
33b5cebe 132 cp_form = forms.ChangeForgotPassForm(formdata_vars)
aeae6cc2
RE
133
134 if request.method == 'POST' and cp_form.validate():
135 user.pw_hash = tools.bcrypt_gen_password_hash(
136 cp_form.password.data)
137 user.save()
138
139 messages.add_message(
140 request,
141 messages.INFO,
142 _("You can now log in using your new password."))
143 return redirect(request, 'mediagoblin.auth.login')
144 else:
145 return render_to_response(
146 request,
147 'mediagoblin/plugins/basic_auth/change_fp.html',
148 {'cp_form': cp_form})
149
6180e3a9
CAW
150 ## Commenting this out temporarily because I'm checking into
151 ## what's going on with user.email_verified.
152 ##
153 ## ... if this commit lasts long enough for anyone but me (cwebber) to
154 ## notice it, they should pester me to remove this or remove it
155 ## themselves ;)
156 #
157 # if not user.email_verified:
158 # messages.add_message(
5c7b2a63
AB
159 # request,
160 # messages.ERROR,
6180e3a9
CAW
161 # _('You need to verify your email before you can reset your'
162 # ' password.'))
aeae6cc2
RE
163
164 if not user.status == 'active':
165 messages.add_message(
5c7b2a63
AB
166 request,
167 messages.ERROR,
168 _("You are no longer an active user. Please contact the system "
169 "admin to reactivate your account."))
aeae6cc2
RE
170
171 return redirect(
172 request, 'index')
173
174
175def _process_for_token(request):
176 """
177 Checks for tokens in formdata without prior knowledge of request method
178
179 For now, returns whether the userid and token formdata variables exist, and
180 the formdata variables in a hash. Perhaps an object is warranted?
181 """
182 # retrieve the formdata variables
183 if request.method == 'GET':
184 formdata_vars = request.GET
185 else:
186 formdata_vars = request.form
187
188 formdata = {
189 'vars': formdata_vars,
190 'has_token': 'token' in formdata_vars}
191
192 return formdata
af665c4e
RE
193
194
195@require_active_login
196def change_pass(request):
197 form = forms.ChangePassForm(request.form)
198 user = request.user
199
200 if request.method == 'POST' and form.validate():
201
202 if not tools.bcrypt_check_password(
203 form.old_password.data, user.pw_hash):
204 form.old_password.errors.append(
205 _('Wrong password'))
206
207 return render_to_response(
208 request,
209 'mediagoblin/plugins/basic_auth/change_pass.html',
210 {'form': form,
211 'user': user})
212
213 # Password matches
214 user.pw_hash = tools.bcrypt_gen_password_hash(
215 form.new_password.data)
216 user.save()
217
218 messages.add_message(
5c7b2a63
AB
219 request,
220 messages.SUCCESS,
af665c4e
RE
221 _('Your password was changed successfully'))
222
223 return redirect(request, 'mediagoblin.edit.account')
224
225 return render_to_response(
226 request,
227 'mediagoblin/plugins/basic_auth/change_pass.html',
228 {'form': form,
229 'user': user})