Commit | Line | Data |
---|---|---|
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/>. | |
16 | from itsdangerous import BadSignature | |
17 | ||
18 | from mediagoblin import messages | |
d88fcb03 | 19 | from mediagoblin.db.models import LocalUser |
af665c4e | 20 | from mediagoblin.decorators import require_active_login |
aeae6cc2 RE |
21 | from mediagoblin.plugins.basic_auth import forms, tools |
22 | from mediagoblin.tools.crypto import get_timed_signer_url | |
23 | from mediagoblin.tools.mail import email_debug_message | |
24 | from mediagoblin.tools.response import redirect, render_to_response, render_404 | |
25 | from mediagoblin.tools.translate import pass_to_ugettext as _ | |
26 | ||
27 | ||
28 | def 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 | ||
93 | def 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 | ||
175 | def _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 | |
196 | def 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}) |