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: | |
63 | messages.add_message(request, | |
64 | messages.WARNING, | |
65 | _("Couldn't find someone with that username.")) | |
64c035b3 CAW |
66 | return redirect(request, |
67 | 'mediagoblin.plugins.basic_auth.forgot_password') | |
aeae6cc2 RE |
68 | |
69 | success_message = _("An email has been sent with instructions " | |
70 | "on how to change your password.") | |
71 | ||
6180e3a9 | 72 | if user and user.has_privilege(u'active') is False: |
aeae6cc2 RE |
73 | # Don't send reminder because user is inactive or has no verified email |
74 | messages.add_message(request, | |
75 | messages.WARNING, | |
76 | _("Could not send password recovery email as your username is in" | |
77 | "active or your account's email address has not been verified.")) | |
78 | ||
79 | return redirect(request, 'mediagoblin.user_pages.user_home', | |
80 | user=user.username) | |
81 | ||
82 | # SUCCESS. Send reminder and return to login page | |
83 | if user: | |
84 | email_debug_message(request) | |
85 | tools.send_fp_verification_email(user, request) | |
86 | ||
87 | messages.add_message(request, messages.INFO, success_message) | |
88 | return redirect(request, 'mediagoblin.auth.login') | |
89 | ||
90 | ||
91 | def verify_forgot_password(request): | |
92 | """ | |
93 | Check the forgot-password verification and possibly let the user | |
94 | change their password because of it. | |
95 | """ | |
96 | # get form data variables, and specifically check for presence of token | |
97 | formdata = _process_for_token(request) | |
98 | if not formdata['has_token']: | |
99 | return render_404(request) | |
100 | ||
101 | formdata_vars = formdata['vars'] | |
102 | ||
103 | # Catch error if token is faked or expired | |
104 | try: | |
105 | token = get_timed_signer_url("mail_verification_token") \ | |
106 | .loads(formdata_vars['token'], max_age=10*24*3600) | |
107 | except BadSignature: | |
108 | messages.add_message( | |
109 | request, | |
110 | messages.ERROR, | |
111 | _('The verification key or user id is incorrect.')) | |
112 | ||
113 | return redirect( | |
114 | request, | |
115 | 'index') | |
116 | ||
117 | # check if it's a valid user id | |
d88fcb03 | 118 | user = LocalUser.query.filter_by(id=int(token)).first() |
aeae6cc2 RE |
119 | |
120 | # no user in db | |
121 | if not user: | |
122 | messages.add_message( | |
123 | request, messages.ERROR, | |
124 | _('The user id is incorrect.')) | |
125 | return redirect( | |
126 | request, 'index') | |
127 | ||
128 | # check if user active and has email verified | |
6180e3a9 | 129 | if user.has_privilege(u'active'): |
33b5cebe | 130 | cp_form = forms.ChangeForgotPassForm(formdata_vars) |
aeae6cc2 RE |
131 | |
132 | if request.method == 'POST' and cp_form.validate(): | |
133 | user.pw_hash = tools.bcrypt_gen_password_hash( | |
134 | cp_form.password.data) | |
135 | user.save() | |
136 | ||
137 | messages.add_message( | |
138 | request, | |
139 | messages.INFO, | |
140 | _("You can now log in using your new password.")) | |
141 | return redirect(request, 'mediagoblin.auth.login') | |
142 | else: | |
143 | return render_to_response( | |
144 | request, | |
145 | 'mediagoblin/plugins/basic_auth/change_fp.html', | |
146 | {'cp_form': cp_form}) | |
147 | ||
6180e3a9 CAW |
148 | ## Commenting this out temporarily because I'm checking into |
149 | ## what's going on with user.email_verified. | |
150 | ## | |
151 | ## ... if this commit lasts long enough for anyone but me (cwebber) to | |
152 | ## notice it, they should pester me to remove this or remove it | |
153 | ## themselves ;) | |
154 | # | |
155 | # if not user.email_verified: | |
156 | # messages.add_message( | |
157 | # request, messages.ERROR, | |
158 | # _('You need to verify your email before you can reset your' | |
159 | # ' password.')) | |
aeae6cc2 RE |
160 | |
161 | if not user.status == 'active': | |
162 | messages.add_message( | |
163 | request, messages.ERROR, | |
164 | _('You are no longer an active user. Please contact the system' | |
4491174d | 165 | ' admin to reactivate your account.')) |
aeae6cc2 RE |
166 | |
167 | return redirect( | |
168 | request, 'index') | |
169 | ||
170 | ||
171 | def _process_for_token(request): | |
172 | """ | |
173 | Checks for tokens in formdata without prior knowledge of request method | |
174 | ||
175 | For now, returns whether the userid and token formdata variables exist, and | |
176 | the formdata variables in a hash. Perhaps an object is warranted? | |
177 | """ | |
178 | # retrieve the formdata variables | |
179 | if request.method == 'GET': | |
180 | formdata_vars = request.GET | |
181 | else: | |
182 | formdata_vars = request.form | |
183 | ||
184 | formdata = { | |
185 | 'vars': formdata_vars, | |
186 | 'has_token': 'token' in formdata_vars} | |
187 | ||
188 | return formdata | |
af665c4e RE |
189 | |
190 | ||
191 | @require_active_login | |
192 | def change_pass(request): | |
193 | form = forms.ChangePassForm(request.form) | |
194 | user = request.user | |
195 | ||
196 | if request.method == 'POST' and form.validate(): | |
197 | ||
198 | if not tools.bcrypt_check_password( | |
199 | form.old_password.data, user.pw_hash): | |
200 | form.old_password.errors.append( | |
201 | _('Wrong password')) | |
202 | ||
203 | return render_to_response( | |
204 | request, | |
205 | 'mediagoblin/plugins/basic_auth/change_pass.html', | |
206 | {'form': form, | |
207 | 'user': user}) | |
208 | ||
209 | # Password matches | |
210 | user.pw_hash = tools.bcrypt_gen_password_hash( | |
211 | form.new_password.data) | |
212 | user.save() | |
213 | ||
214 | messages.add_message( | |
215 | request, messages.SUCCESS, | |
216 | _('Your password was changed successfully')) | |
217 | ||
218 | return redirect(request, 'mediagoblin.edit.account') | |
219 | ||
220 | return render_to_response( | |
221 | request, | |
222 | 'mediagoblin/plugins/basic_auth/change_pass.html', | |
223 | {'form': form, | |
224 | 'user': user}) |