Merge remote-tracking branch 'remotes/lorochka85/bug852_use_media_slug_instead_of_id'
[mediagoblin.git] / mediagoblin / auth / views.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 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
17 import uuid
18 import datetime
19
20 from webob import exc
21
22 from mediagoblin import messages
23 from mediagoblin import mg_globals
24 from mediagoblin.tools.response import render_to_response, redirect, render_404
25 from mediagoblin.tools.translate import pass_to_ugettext as _
26 from mediagoblin.db.util import ObjectId, InvalidId
27 from mediagoblin.auth import lib as auth_lib
28 from mediagoblin.auth import forms as auth_forms
29 from mediagoblin.auth.lib import send_verification_email, \
30 send_fp_verification_email
31
32
33 def email_debug_message(request):
34 """
35 If the server is running in email debug mode (which is
36 the current default), give a debug message to the user
37 so that they have an idea where to find their email.
38 """
39 if mg_globals.app_config['email_debug_mode']:
40 # DEBUG message, no need to translate
41 messages.add_message(request, messages.DEBUG,
42 u"This instance is running in email debug mode. "
43 u"The email will be on the console of the server process.")
44
45
46 def register(request):
47 """
48 Your classic registration view!
49 """
50 # Redirects to indexpage if registrations are disabled
51 if not mg_globals.app_config["allow_registration"]:
52 messages.add_message(
53 request,
54 messages.WARNING,
55 _('Sorry, registration is disabled on this instance.'))
56 return redirect(request, "index")
57
58 register_form = auth_forms.RegistrationForm(request.POST)
59
60 if request.method == 'POST' and register_form.validate():
61 # TODO: Make sure the user doesn't exist already
62 username = unicode(request.POST['username'].lower())
63 email = unicode(request.POST['email'].lower())
64 users_with_username = request.db.User.find(
65 {'username': username}).count()
66 users_with_email = request.db.User.find(
67 {'email': email}).count()
68
69 extra_validation_passes = True
70
71 if users_with_username:
72 register_form.username.errors.append(
73 _(u'Sorry, a user with that name already exists.'))
74 extra_validation_passes = False
75 if users_with_email:
76 register_form.email.errors.append(
77 _(u'Sorry, a user with that email address already exists.'))
78 extra_validation_passes = False
79
80 if extra_validation_passes:
81 # Create the user
82 user = request.db.User()
83 user['username'] = username
84 user['email'] = email
85 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
86 request.POST['password'])
87 user.save(validate=True)
88
89 # log the user in
90 request.session['user_id'] = unicode(user._id)
91 request.session.save()
92
93 # send verification email
94 email_debug_message(request)
95 send_verification_email(user, request)
96
97 # redirect the user to their homepage... there will be a
98 # message waiting for them to verify their email
99 return redirect(
100 request, 'mediagoblin.user_pages.user_home',
101 user=user['username'])
102
103 return render_to_response(
104 request,
105 'mediagoblin/auth/register.html',
106 {'register_form': register_form})
107
108
109 def login(request):
110 """
111 MediaGoblin login view.
112
113 If you provide the POST with 'next', it'll redirect to that view.
114 """
115 login_form = auth_forms.LoginForm(request.POST)
116
117 login_failed = False
118
119 if request.method == 'POST' and login_form.validate():
120 user = request.db.User.one(
121 {'username': request.POST['username'].lower()})
122
123 if user and user.check_login(request.POST['password']):
124 # set up login in session
125 request.session['user_id'] = unicode(user._id)
126 request.session.save()
127
128 if request.POST.get('next'):
129 return exc.HTTPFound(location=request.POST['next'])
130 else:
131 return redirect(request, "index")
132
133 else:
134 # Prevent detecting who's on this system by testing login
135 # attempt timings
136 auth_lib.fake_login_attempt()
137 login_failed = True
138
139 return render_to_response(
140 request,
141 'mediagoblin/auth/login.html',
142 {'login_form': login_form,
143 'next': request.GET.get('next') or request.POST.get('next'),
144 'login_failed': login_failed,
145 'allow_registration': mg_globals.app_config["allow_registration"]})
146
147
148 def logout(request):
149 # Maybe deleting the user_id parameter would be enough?
150 request.session.delete()
151
152 return redirect(request, "index")
153
154
155 def verify_email(request):
156 """
157 Email verification view
158
159 validates GET parameters against database and unlocks the user account, if
160 you are lucky :)
161 """
162 # If we don't have userid and token parameters, we can't do anything; 404
163 if not 'userid' in request.GET or not 'token' in request.GET:
164 return render_404(request)
165
166 user = request.db.User.find_one(
167 {'_id': ObjectId(unicode(request.GET['userid']))})
168
169 if user and user['verification_key'] == unicode(request.GET['token']):
170 user[u'status'] = u'active'
171 user[u'email_verified'] = True
172 user[u'verification_key'] = None
173
174 user.save()
175
176 messages.add_message(
177 request,
178 messages.SUCCESS,
179 _("Your email address has been verified. "
180 "You may now login, edit your profile, and submit images!"))
181 else:
182 messages.add_message(
183 request,
184 messages.ERROR,
185 _('The verification key or user id is incorrect'))
186
187 return redirect(
188 request, 'mediagoblin.user_pages.user_home',
189 user=user['username'])
190
191
192 def resend_activation(request):
193 """
194 The reactivation view
195
196 Resend the activation email.
197 """
198
199 if request.user is None:
200 messages.add_message(
201 request,
202 messages.ERROR,
203 _('You must be logged in so we know who to send the email to!'))
204
205 return redirect(request, 'mediagoblin.auth.login')
206
207 if request.user["email_verified"]:
208 messages.add_message(
209 request,
210 messages.ERROR,
211 _("You've already verified your email address!"))
212
213 return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
214
215 request.user[u'verification_key'] = unicode(uuid.uuid4())
216 request.user.save()
217
218 email_debug_message(request)
219 send_verification_email(request.user, request)
220
221 messages.add_message(
222 request,
223 messages.INFO,
224 _('Resent your verification email.'))
225 return redirect(
226 request, 'mediagoblin.user_pages.user_home',
227 user=request.user['username'])
228
229
230 def forgot_password(request):
231 """
232 Forgot password view
233
234 Sends an email whit an url to renew forgoten password
235 """
236 fp_form = auth_forms.ForgotPassForm(request.POST)
237
238 if request.method == 'POST' and fp_form.validate():
239
240 # Here, so it doesn't depend on the actual mail being sent
241 # and thus doesn't reveal, wether mail was sent.
242 email_debug_message(request)
243
244 # '$or' not available till mongodb 1.5.3
245 user = request.db.User.find_one(
246 {'username': request.POST['username']})
247 if not user:
248 user = request.db.User.find_one(
249 {'email': request.POST['username']})
250
251 if user:
252 if user['email_verified'] and user['status'] == 'active':
253 user[u'fp_verification_key'] = unicode(uuid.uuid4())
254 user[u'fp_token_expire'] = datetime.datetime.now() + \
255 datetime.timedelta(days=10)
256 user.save()
257
258 send_fp_verification_email(user, request)
259 else:
260 # special case... we can't send the email because the
261 # username is inactive / hasn't verified their email
262 messages.add_message(
263 request,
264 messages.WARNING,
265 _("Could not send password recovery email as "
266 "your username is inactive or your account's "
267 "email address has not been verified."))
268
269 return redirect(
270 request, 'mediagoblin.user_pages.user_home',
271 user=user['username'])
272
273 # do not reveal whether or not there is a matching user
274 return redirect(request, 'mediagoblin.auth.fp_email_sent')
275
276 return render_to_response(
277 request,
278 'mediagoblin/auth/forgot_password.html',
279 {'fp_form': fp_form})
280
281
282 def verify_forgot_password(request):
283 """
284 Check the forgot-password verification and possibly let the user
285 change their password because of it.
286 """
287 # get form data variables, and specifically check for presence of token
288 formdata = _process_for_token(request)
289 if not formdata['has_userid_and_token']:
290 return render_404(request)
291
292 formdata_token = formdata['vars']['token']
293 formdata_userid = formdata['vars']['userid']
294 formdata_vars = formdata['vars']
295
296 # check if it's a valid Id
297 try:
298 user = request.db.User.find_one(
299 {'_id': ObjectId(unicode(formdata_userid))})
300 except InvalidId:
301 return render_404(request)
302
303 # check if we have a real user and correct token
304 if ((user and user['fp_verification_key'] and
305 user['fp_verification_key'] == unicode(formdata_token) and
306 datetime.datetime.now() < user['fp_token_expire']
307 and user['email_verified'] and user['status'] == 'active')):
308
309 cp_form = auth_forms.ChangePassForm(formdata_vars)
310
311 if request.method == 'POST' and cp_form.validate():
312 user[u'pw_hash'] = auth_lib.bcrypt_gen_password_hash(
313 request.POST['password'])
314 user[u'fp_verification_key'] = None
315 user[u'fp_token_expire'] = None
316 user.save()
317
318 return redirect(request, 'mediagoblin.auth.fp_changed_success')
319 else:
320 return render_to_response(
321 request,
322 'mediagoblin/auth/change_fp.html',
323 {'cp_form': cp_form})
324
325 # in case there is a valid id but no user whit that id in the db
326 # or the token expired
327 else:
328 return render_404(request)
329
330
331 def _process_for_token(request):
332 """
333 Checks for tokens in formdata without prior knowledge of request method
334
335 For now, returns whether the userid and token formdata variables exist, and
336 the formdata variables in a hash. Perhaps an object is warranted?
337 """
338 # retrieve the formdata variables
339 if request.method == 'GET':
340 formdata_vars = request.GET
341 else:
342 formdata_vars = request.POST
343
344 formdata = {
345 'vars': formdata_vars,
346 'has_userid_and_token':
347 'userid' in formdata_vars and 'token' in formdata_vars}
348
349 return formdata