1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
20 from mediagoblin
import messages
, mg_globals
21 from mediagoblin
.db
.models
import User
22 from mediagoblin
.tools
.response
import render_to_response
, redirect
, render_404
23 from mediagoblin
.tools
.translate
import pass_to_ugettext
as _
24 from mediagoblin
.auth
import lib
as auth_lib
25 from mediagoblin
.auth
import forms
as auth_forms
26 from mediagoblin
.auth
.lib
import send_verification_email
, \
27 send_fp_verification_email
30 def email_debug_message(request
):
32 If the server is running in email debug mode (which is
33 the current default), give a debug message to the user
34 so that they have an idea where to find their email.
36 if mg_globals
.app_config
['email_debug_mode']:
37 # DEBUG message, no need to translate
38 messages
.add_message(request
, messages
.DEBUG
,
39 u
"This instance is running in email debug mode. "
40 u
"The email will be on the console of the server process.")
43 def register(request
):
44 """The registration view.
46 Note that usernames will always be lowercased. Email domains are lowercased while
47 the first part remains case-sensitive.
49 # Redirects to indexpage if registrations are disabled
50 if not mg_globals
.app_config
["allow_registration"]:
54 _('Sorry, registration is disabled on this instance.'))
55 return redirect(request
, "index")
57 register_form
= auth_forms
.RegistrationForm(request
.form
)
59 if request
.method
== 'POST' and register_form
.validate():
60 # TODO: Make sure the user doesn't exist already
61 users_with_username
= User
.query
.filter_by(username
=register_form
.data
['username']).count()
62 users_with_email
= User
.query
.filter_by(email
=register_form
.data
['email']).count()
64 extra_validation_passes
= True
66 if users_with_username
:
67 register_form
.username
.errors
.append(
68 _(u
'Sorry, a user with that name already exists.'))
69 extra_validation_passes
= False
71 register_form
.email
.errors
.append(
72 _(u
'Sorry, a user with that email address already exists.'))
73 extra_validation_passes
= False
75 if extra_validation_passes
:
78 user
.username
= register_form
.data
['username']
79 user
.email
= register_form
.data
['email']
80 user
.pw_hash
= auth_lib
.bcrypt_gen_password_hash(
81 register_form
.password
.data
)
82 user
.verification_key
= unicode(uuid
.uuid4())
86 request
.session
['user_id'] = unicode(user
.id)
87 request
.session
.save()
89 # send verification email
90 email_debug_message(request
)
91 send_verification_email(user
, request
)
93 # redirect the user to their homepage... there will be a
94 # message waiting for them to verify their email
96 request
, 'mediagoblin.user_pages.user_home',
99 return render_to_response(
101 'mediagoblin/auth/register.html',
102 {'register_form': register_form
})
107 MediaGoblin login view.
109 If you provide the POST with 'next', it'll redirect to that view.
111 login_form
= auth_forms
.LoginForm(request
.form
)
115 if request
.method
== 'POST':
116 if login_form
.validate():
117 user
= User
.query
.filter_by(username
=login_form
.data
['username']).first()
119 if user
and user
.check_login(login_form
.password
.data
):
120 # set up login in session
121 request
.session
['user_id'] = unicode(user
.id)
122 request
.session
.save()
124 if request
.form
.get('next'):
125 return redirect(request
, location
=request
.form
['next'])
127 return redirect(request
, "index")
129 # Some failure during login occured if we are here!
130 # Prevent detecting who's on this system by testing login
132 auth_lib
.fake_login_attempt()
135 return render_to_response(
137 'mediagoblin/auth/login.html',
138 {'login_form': login_form
,
139 'next': request
.GET
.get('next') or request
.form
.get('next'),
140 'login_failed': login_failed
,
141 'allow_registration': mg_globals
.app_config
["allow_registration"]})
145 # Maybe deleting the user_id parameter would be enough?
146 request
.session
.delete()
148 return redirect(request
, "index")
151 def verify_email(request
):
153 Email verification view
155 validates GET parameters against database and unlocks the user account, if
158 # If we don't have userid and token parameters, we can't do anything; 404
159 if not 'userid' in request
.GET
or not 'token' in request
.GET
:
160 return render_404(request
)
162 user
= User
.query
.filter_by(id=request
.args
['userid']).first()
164 if user
and user
.verification_key
== unicode(request
.GET
['token']):
165 user
.status
= u
'active'
166 user
.email_verified
= True
167 user
.verification_key
= None
171 messages
.add_message(
174 _("Your email address has been verified. "
175 "You may now login, edit your profile, and submit images!"))
177 messages
.add_message(
180 _('The verification key or user id is incorrect'))
183 request
, 'mediagoblin.user_pages.user_home',
187 def resend_activation(request
):
189 The reactivation view
191 Resend the activation email.
194 if request
.user
is None:
195 messages
.add_message(
198 _('You must be logged in so we know who to send the email to!'))
200 return redirect(request
, 'mediagoblin.auth.login')
202 if request
.user
.email_verified
:
203 messages
.add_message(
206 _("You've already verified your email address!"))
208 return redirect(request
, "mediagoblin.user_pages.user_home", user
=request
.user
['username'])
210 request
.user
.verification_key
= unicode(uuid
.uuid4())
213 email_debug_message(request
)
214 send_verification_email(request
.user
, request
)
216 messages
.add_message(
219 _('Resent your verification email.'))
221 request
, 'mediagoblin.user_pages.user_home',
222 user
=request
.user
.username
)
225 def forgot_password(request
):
229 Sends an email with an url to renew forgotten password.
230 Use GET querystring parameter 'username' to pre-populate the input field
232 fp_form
= auth_forms
.ForgotPassForm(request
.form
,
233 username
=request
.args
.get('username'))
235 if not (request
.method
== 'POST' and fp_form
.validate()):
236 # Either GET request, or invalid form submitted. Display the template
237 return render_to_response(request
,
238 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form
})
240 # If we are here: method == POST and form is valid. username casing
241 # has been sanitized. Store if a user was found by email. We should
242 # not reveal if the operation was successful then as we don't want to
243 # leak if an email address exists in the system.
244 found_by_email
= '@' in fp_form
.username
.data
247 user
= User
.query
.filter_by(
248 email
= fp_form
.username
.data
).first()
249 # Don't reveal success in case the lookup happened by email address.
250 success_message
=_("If that email address (case sensitive!) is "
251 "registered an email has been sent with instructions "
252 "on how to change your password.")
254 else: # found by username
255 user
= User
.query
.filter_by(
256 username
= fp_form
.username
.data
).first()
259 messages
.add_message(request
,
261 _("Couldn't find someone with that username."))
262 return redirect(request
, 'mediagoblin.auth.forgot_password')
264 success_message
=_("An email has been sent with instructions "
265 "on how to change your password.")
267 if user
and not(user
.email_verified
and user
.status
== 'active'):
268 # Don't send reminder because user is inactive or has no verified email
269 messages
.add_message(request
,
271 _("Could not send password recovery email as your username is in"
272 "active or your account's email address has not been verified."))
274 return redirect(request
, 'mediagoblin.user_pages.user_home',
277 # SUCCESS. Send reminder and return to login page
279 user
.fp_verification_key
= unicode(uuid
.uuid4())
280 user
.fp_token_expire
= datetime
.datetime
.now() + \
281 datetime
.timedelta(days
=10)
284 email_debug_message(request
)
285 send_fp_verification_email(user
, request
)
287 messages
.add_message(request
, messages
.INFO
, success_message
)
288 return redirect(request
, 'mediagoblin.auth.login')
291 def verify_forgot_password(request
):
293 Check the forgot-password verification and possibly let the user
294 change their password because of it.
296 # get form data variables, and specifically check for presence of token
297 formdata
= _process_for_token(request
)
298 if not formdata
['has_userid_and_token']:
299 return render_404(request
)
301 formdata_token
= formdata
['vars']['token']
302 formdata_userid
= formdata
['vars']['userid']
303 formdata_vars
= formdata
['vars']
305 # check if it's a valid user id
306 user
= User
.query
.filter_by(id=formdata_userid
).first()
308 return render_404(request
)
310 # check if we have a real user and correct token
311 if ((user
and user
.fp_verification_key
and
312 user
.fp_verification_key
== unicode(formdata_token
) and
313 datetime
.datetime
.now() < user
.fp_token_expire
314 and user
.email_verified
and user
.status
== 'active')):
316 cp_form
= auth_forms
.ChangePassForm(formdata_vars
)
318 if request
.method
== 'POST' and cp_form
.validate():
319 user
.pw_hash
= auth_lib
.bcrypt_gen_password_hash(
320 cp_form
.password
.data
)
321 user
.fp_verification_key
= None
322 user
.fp_token_expire
= None
325 messages
.add_message(
328 _("You can now log in using your new password."))
329 return redirect(request
, 'mediagoblin.auth.login')
331 return render_to_response(
333 'mediagoblin/auth/change_fp.html',
334 {'cp_form': cp_form
})
336 # in case there is a valid id but no user with that id in the db
337 # or the token expired
339 return render_404(request
)
342 def _process_for_token(request
):
344 Checks for tokens in formdata without prior knowledge of request method
346 For now, returns whether the userid and token formdata variables exist, and
347 the formdata variables in a hash. Perhaps an object is warranted?
349 # retrieve the formdata variables
350 if request
.method
== 'GET':
351 formdata_vars
= request
.GET
353 formdata_vars
= request
.form
356 'vars': formdata_vars
,
357 'has_userid_and_token':
358 'userid' in formdata_vars
and 'token' in formdata_vars
}