Commit | Line | Data |
---|---|---|
8e1e744d | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
24181820 CAW |
2 | # Copyright (C) 2011 Free Software Foundation, Inc |
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 | 17 | import uuid |
25ba955e | 18 | import datetime |
a77d952a | 19 | |
1c63ad5d | 20 | from webob import exc |
24181820 | 21 | |
cfe46f3e | 22 | from mediagoblin import messages |
13677ef9 | 23 | from mediagoblin import mg_globals |
de12b4e7 | 24 | from mediagoblin.util import render_to_response, redirect, render_404 |
4b1adc13 | 25 | from mediagoblin.util import pass_to_ugettext as _ |
25ba955e | 26 | from mediagoblin.db.util import ObjectId, InvalidId |
24181820 CAW |
27 | from mediagoblin.auth import lib as auth_lib |
28 | from mediagoblin.auth import forms as auth_forms | |
25ba955e AV |
29 | from mediagoblin.auth.lib import send_verification_email, \ |
30 | send_fp_verification_email | |
24181820 CAW |
31 | |
32 | ||
33 | def register(request): | |
34 | """ | |
35 | Your classic registration view! | |
36 | """ | |
13677ef9 RL |
37 | # Redirects to indexpage if registrations are disabled |
38 | if not mg_globals.app_config["allow_registration"]: | |
166dc91a CAW |
39 | messages.add_message( |
40 | request, | |
41 | messages.WARNING, | |
4b1adc13 | 42 | _('Sorry, registration is disabled on this instance.')) |
13677ef9 RL |
43 | return redirect(request, "index") |
44 | ||
24181820 CAW |
45 | register_form = auth_forms.RegistrationForm(request.POST) |
46 | ||
47 | if request.method == 'POST' and register_form.validate(): | |
48 | # TODO: Make sure the user doesn't exist already | |
ce72a1bb | 49 | |
dc49cf60 CAW |
50 | users_with_username = request.db.User.find( |
51 | {'username': request.POST['username'].lower()}).count() | |
0bf099d7 AV |
52 | users_with_email = request.db.User.find( |
53 | {'email': request.POST['email'].lower()}).count() | |
24181820 | 54 | |
9f6ea475 CAW |
55 | extra_validation_passes = True |
56 | ||
24181820 CAW |
57 | if users_with_username: |
58 | register_form.username.errors.append( | |
4b1adc13 | 59 | _(u'Sorry, a user with that name already exists.')) |
9f6ea475 CAW |
60 | extra_validation_passes = False |
61 | if users_with_email: | |
0bf099d7 AV |
62 | register_form.email.errors.append( |
63 | _(u'Sorry, that email address has already been taken.')) | |
9f6ea475 | 64 | extra_validation_passes = False |
24181820 | 65 | |
9f6ea475 | 66 | if extra_validation_passes: |
24181820 | 67 | # Create the user |
0bc03620 CAW |
68 | user = request.db.User() |
69 | user['username'] = request.POST['username'].lower() | |
873e4e9d | 70 | user['email'] = request.POST['email'].lower() |
0bc03620 | 71 | user['pw_hash'] = auth_lib.bcrypt_gen_password_hash( |
24181820 | 72 | request.POST['password']) |
0bc03620 CAW |
73 | user.save(validate=True) |
74 | ||
f73f4c4b CAW |
75 | # log the user in |
76 | request.session['user_id'] = unicode(user['_id']) | |
77 | request.session.save() | |
78 | ||
79 | # send verification email | |
0bc03620 CAW |
80 | send_verification_email(user, request) |
81 | ||
dce5c9cb CAW |
82 | # redirect the user to their homepage... there will be a |
83 | # message waiting for them to verify their email | |
0bc03620 CAW |
84 | return redirect( |
85 | request, 'mediagoblin.user_pages.user_home', | |
86 | user=user['username']) | |
24181820 | 87 | |
9038c9f9 CAW |
88 | return render_to_response( |
89 | request, | |
c9c24934 E |
90 | 'mediagoblin/auth/register.html', |
91 | {'register_form': register_form}) | |
24181820 CAW |
92 | |
93 | ||
692fd1c9 | 94 | def login(request): |
a3776717 | 95 | """ |
8e1e744d | 96 | MediaGoblin login view. |
a3776717 CAW |
97 | |
98 | If you provide the POST with 'next', it'll redirect to that view. | |
99 | """ | |
692fd1c9 CAW |
100 | login_form = auth_forms.LoginForm(request.POST) |
101 | ||
a3776717 CAW |
102 | login_failed = False |
103 | ||
692fd1c9 | 104 | if request.method == 'POST' and login_form.validate(): |
b058cf15 | 105 | user = request.db.User.one( |
ce72a1bb | 106 | {'username': request.POST['username'].lower()}) |
692fd1c9 | 107 | |
d1938963 | 108 | if user and user.check_login(request.POST['password']): |
692fd1c9 CAW |
109 | # set up login in session |
110 | request.session['user_id'] = unicode(user['_id']) | |
a3776717 | 111 | request.session.save() |
692fd1c9 | 112 | |
574d1511 | 113 | if request.POST.get('next'): |
a3776717 CAW |
114 | return exc.HTTPFound(location=request.POST['next']) |
115 | else: | |
9150244a | 116 | return redirect(request, "index") |
692fd1c9 CAW |
117 | |
118 | else: | |
119 | # Prevent detecting who's on this system by testing login | |
120 | # attempt timings | |
121 | auth_lib.fake_login_attempt() | |
a3776717 | 122 | login_failed = True |
692fd1c9 | 123 | |
9038c9f9 CAW |
124 | return render_to_response( |
125 | request, | |
c9c24934 E |
126 | 'mediagoblin/auth/login.html', |
127 | {'login_form': login_form, | |
128 | 'next': request.GET.get('next') or request.POST.get('next'), | |
13bb1d67 RL |
129 | 'login_failed': login_failed, |
130 | 'allow_registration': mg_globals.app_config["allow_registration"]}) | |
692fd1c9 CAW |
131 | |
132 | ||
133 | def logout(request): | |
b97232fa CAW |
134 | # Maybe deleting the user_id parameter would be enough? |
135 | request.session.delete() | |
7b31a11c | 136 | |
9150244a | 137 | return redirect(request, "index") |
db1a438f | 138 | |
5866d1a8 | 139 | |
db1a438f | 140 | def verify_email(request): |
4c093e85 JW |
141 | """ |
142 | Email verification view | |
143 | ||
144 | validates GET parameters against database and unlocks the user account, if | |
145 | you are lucky :) | |
146 | """ | |
155f24f9 CAW |
147 | # If we don't have userid and token parameters, we can't do anything; 404 |
148 | if not request.GET.has_key('userid') or not request.GET.has_key('token'): | |
de12b4e7 | 149 | return render_404(request) |
155f24f9 | 150 | |
db1a438f | 151 | user = request.db.User.find_one( |
e0f84870 | 152 | {'_id': ObjectId(unicode(request.GET['userid']))}) |
db1a438f | 153 | |
155f24f9 | 154 | if user and user['verification_key'] == unicode(request.GET['token']): |
db1a438f JW |
155 | user['status'] = u'active' |
156 | user['email_verified'] = True | |
db1a438f | 157 | user.save() |
fe80cb06 | 158 | messages.add_message( |
7b31a11c CAW |
159 | request, |
160 | messages.SUCCESS, | |
4b1adc13 CAW |
161 | _("Your email address has been verified. " |
162 | "You may now login, edit your profile, and submit images!")) | |
db1a438f | 163 | else: |
4b1adc13 CAW |
164 | messages.add_message( |
165 | request, | |
166 | messages.ERROR, | |
167 | _('The verification key or user id is incorrect')) | |
7b31a11c | 168 | |
269943a6 CAW |
169 | return redirect( |
170 | request, 'mediagoblin.user_pages.user_home', | |
788272f3 | 171 | user=user['username']) |
28afb47c | 172 | |
5866d1a8 | 173 | |
b93a6a22 AM |
174 | def resend_activation(request): |
175 | """ | |
176 | The reactivation view | |
177 | ||
178 | Resend the activation email. | |
179 | """ | |
a77d952a CAW |
180 | request.user['verification_key'] = unicode(uuid.uuid4()) |
181 | request.user.save() | |
b93a6a22 | 182 | |
02d80437 | 183 | send_verification_email(request.user, request) |
b93a6a22 | 184 | |
61927e6e CAW |
185 | messages.add_message( |
186 | request, | |
187 | messages.INFO, | |
4b1adc13 | 188 | _('Resent your verification email.')) |
61927e6e CAW |
189 | return redirect( |
190 | request, 'mediagoblin.user_pages.user_home', | |
191 | user=request.user['username']) | |
25ba955e AV |
192 | |
193 | ||
194 | def forgot_password(request): | |
195 | """ | |
196 | Forgot password view | |
197 | ||
198 | Sends an email whit an url to renew forgoten password | |
199 | """ | |
200 | fp_form = auth_forms.ForgotPassForm(request.POST) | |
201 | ||
202 | if request.method == 'POST' and fp_form.validate(): | |
d1a64326 CAW |
203 | # '$or' not available till mongodb 1.5.3 |
204 | user = request.db.User.find_one( | |
205 | {'username': request.POST['username']}) | |
206 | if not user: | |
207 | user = request.db.User.find_one( | |
208 | {'email': request.POST['username']}) | |
25ba955e | 209 | |
24966c43 | 210 | if user: |
a85a2110 CAW |
211 | if user['email_verified'] and user['status'] == 'active': |
212 | user['fp_verification_key'] = unicode(uuid.uuid4()) | |
213 | user['fp_token_expire'] = datetime.datetime.now() + \ | |
214 | datetime.timedelta(days=10) | |
215 | user.save() | |
216 | ||
217 | send_fp_verification_email(user, request) | |
218 | else: | |
219 | # special case... we can't send the email because the | |
220 | # username is inactive / hasn't verified their email | |
221 | messages.add_message( | |
222 | request, | |
223 | messages.WARNING, | |
224 | _("Could not send password recovery email as " | |
225 | "your username is inactive or your account's " | |
226 | "email address has not been verified.")) | |
227 | ||
228 | return redirect( | |
229 | request, 'mediagoblin.user_pages.user_home', | |
230 | user=user['username']) | |
25ba955e | 231 | |
25ba955e | 232 | |
24966c43 CFD |
233 | # do not reveal whether or not there is a matching user, just move along |
234 | return redirect(request, 'mediagoblin.auth.fp_email_sent') | |
25ba955e AV |
235 | |
236 | return render_to_response( | |
2c9e8184 CAW |
237 | request, |
238 | 'mediagoblin/auth/forgot_password.html', | |
239 | {'fp_form': fp_form}) | |
25ba955e AV |
240 | |
241 | ||
242 | def verify_forgot_password(request): | |
8d1c9863 CFD |
243 | # get session variables, and specifically check for presence of token |
244 | mysession = _process_for_token(request) | |
4bcaf9f3 | 245 | if not mysession['has_userid_and_token']: |
8d1c9863 CFD |
246 | return render_404(request) |
247 | ||
248 | session_token = mysession['vars']['token'] | |
249 | session_userid = mysession['vars']['userid'] | |
250 | session_vars = mysession['vars'] | |
251 | ||
252 | # check if it's a valid Id | |
253 | try: | |
254 | user = request.db.User.find_one( | |
255 | {'_id': ObjectId(unicode(session_userid))}) | |
256 | except InvalidId: | |
257 | return render_404(request) | |
258 | ||
259 | # check if we have a real user and correct token | |
73fffbb8 CAW |
260 | if ((user and user['fp_verification_key'] and |
261 | user['fp_verification_key'] == unicode(session_token) and | |
a85a2110 CAW |
262 | datetime.datetime.now() < user['fp_token_expire'] |
263 | and user['email_verified'] and user['status'] == 'active')): | |
73fffbb8 | 264 | |
8d1c9863 CFD |
265 | cp_form = auth_forms.ChangePassForm(session_vars) |
266 | ||
267 | if request.method == 'POST' and cp_form.validate(): | |
268 | user['pw_hash'] = auth_lib.bcrypt_gen_password_hash( | |
269 | request.POST['password']) | |
270 | user['fp_verification_key'] = None | |
271 | user['fp_token_expire'] = None | |
272 | user.save() | |
273 | ||
274 | return redirect(request, 'mediagoblin.auth.fp_changed_success') | |
25ba955e | 275 | else: |
73fffbb8 CAW |
276 | return render_to_response( |
277 | request, | |
278 | 'mediagoblin/auth/change_fp.html', | |
279 | {'cp_form': cp_form}) | |
280 | ||
8d1c9863 CFD |
281 | # in case there is a valid id but no user whit that id in the db |
282 | # or the token expired | |
283 | else: | |
284 | return render_404(request) | |
285 | ||
286 | ||
287 | def _process_for_token(request): | |
288 | """ | |
289 | Checks for tokens in session without prior knowledge of request method | |
290 | ||
291 | For now, returns whether the userid and token session variables exist, and | |
292 | the session variables in a hash. Perhaps an object is warranted? | |
293 | """ | |
294 | # retrieve the session variables | |
295 | if request.method == 'GET': | |
296 | session_vars = request.GET | |
297 | else: | |
298 | session_vars = request.POST | |
299 | ||
2c9e8184 CAW |
300 | mysession = { |
301 | 'vars': session_vars, | |
302 | 'has_userid_and_token': | |
303 | session_vars.has_key('userid') and session_vars.has_key('token')} | |
304 | ||
8d1c9863 | 305 | return mysession |