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
.tools
.mail
import email_debug_message
25 from mediagoblin
.auth
import forms
as auth_forms
26 from mediagoblin
.auth
.tools
import (send_verification_email
, register_user
,
27 send_fp_verification_email
,
29 from mediagoblin
import auth
32 def register(request
):
33 """The registration view.
35 Note that usernames will always be lowercased. Email domains are lowercased while
36 the first part remains case-sensitive.
38 # Redirects to indexpage if registrations are disabled or no authentication
40 if not mg_globals
.app_config
["allow_registration"] or not mg_globals
.app
.auth
:
44 _('Sorry, registration is disabled on this instance.'))
45 return redirect(request
, "index")
47 if 'pass_auth' not in request
.template_env
.globals:
48 if 'openid' in request
.template_env
.globals:
49 return redirect(request
, 'mediagoblin.plugins.openid.register')
51 register_form
= auth
.get_registration_form(request
)
53 if request
.method
== 'POST' and register_form
.validate():
54 # TODO: Make sure the user doesn't exist already
55 user
= register_user(request
, register_form
)
58 # redirect the user to their homepage... there will be a
59 # message waiting for them to verify their email
61 request
, 'mediagoblin.user_pages.user_home',
64 return render_to_response(
66 'mediagoblin/auth/register.html',
67 {'register_form': register_form
,
69 'post_url': request
.urlgen('mediagoblin.auth.register')})
74 MediaGoblin login view.
76 If you provide the POST with 'next', it'll redirect to that view.
78 # Redirects to index page if no authentication is enabled
79 if not mg_globals
.app
.auth
:
83 _('Sorry, authentication is disabled on this instance.'))
84 return redirect(request
, 'index')
86 if 'pass_auth' not in request
.template_env
.globals:
87 if 'openid' in request
.template_env
.globals:
88 return redirect(request
, 'mediagoblin.plugins.openid.login')
90 login_form
= auth
.get_login_form(request
)
94 if request
.method
== 'POST':
95 username
= login_form
.username
.data
97 if login_form
.validate():
98 user
= check_login_simple(username
, login_form
.password
.data
)
100 username
= login_form
.data
['username']
102 if login_form
.validate():
103 user
= check_login_simple(username
, login_form
.password
.data
, True)
106 # set up login in session
107 request
.session
['user_id'] = unicode(user
.id)
108 request
.session
.save()
110 if request
.form
.get('next'):
111 return redirect(request
, location
=request
.form
['next'])
113 return redirect(request
, "index")
117 return render_to_response(
119 'mediagoblin/auth/login.html',
120 {'login_form': login_form
,
121 'next': request
.GET
.get('next') or request
.form
.get('next'),
122 'login_failed': login_failed
,
124 'post_url': request
.urlgen('mediagoblin.auth.login'),
125 'allow_registration': mg_globals
.app_config
["allow_registration"]})
129 # Maybe deleting the user_id parameter would be enough?
130 request
.session
.delete()
132 return redirect(request
, "index")
135 def verify_email(request
):
137 Email verification view
139 validates GET parameters against database and unlocks the user account, if
142 # If we don't have userid and token parameters, we can't do anything; 404
143 if not 'userid' in request
.GET
or not 'token' in request
.GET
:
144 return render_404(request
)
146 user
= User
.query
.filter_by(id=request
.args
['userid']).first()
148 if user
and user
.verification_key
== unicode(request
.GET
['token']):
149 user
.status
= u
'active'
150 user
.email_verified
= True
151 user
.verification_key
= None
155 messages
.add_message(
158 _("Your email address has been verified. "
159 "You may now login, edit your profile, and submit images!"))
161 messages
.add_message(
164 _('The verification key or user id is incorrect'))
167 request
, 'mediagoblin.user_pages.user_home',
171 def resend_activation(request
):
173 The reactivation view
175 Resend the activation email.
178 if request
.user
is None:
179 messages
.add_message(
182 _('You must be logged in so we know who to send the email to!'))
184 return redirect(request
, 'mediagoblin.auth.login')
186 if request
.user
.email_verified
:
187 messages
.add_message(
190 _("You've already verified your email address!"))
192 return redirect(request
, "mediagoblin.user_pages.user_home", user
=request
.user
['username'])
194 request
.user
.verification_key
= unicode(uuid
.uuid4())
197 email_debug_message(request
)
198 send_verification_email(request
.user
, request
)
200 messages
.add_message(
203 _('Resent your verification email.'))
205 request
, 'mediagoblin.user_pages.user_home',
206 user
=request
.user
.username
)
209 def forgot_password(request
):
213 Sends an email with an url to renew forgotten password.
214 Use GET querystring parameter 'username' to pre-populate the input field
216 if not 'pass_auth' in request
.template_env
.globals:
217 return redirect(request
, 'index')
219 fp_form
= auth_forms
.ForgotPassForm(request
.form
,
220 username
=request
.args
.get('username'))
222 if not (request
.method
== 'POST' and fp_form
.validate()):
223 # Either GET request, or invalid form submitted. Display the template
224 return render_to_response(request
,
225 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form
,
226 'focus': 'username'})
228 # If we are here: method == POST and form is valid. username casing
229 # has been sanitized. Store if a user was found by email. We should
230 # not reveal if the operation was successful then as we don't want to
231 # leak if an email address exists in the system.
232 found_by_email
= '@' in fp_form
.username
.data
235 user
= User
.query
.filter_by(
236 email
= fp_form
.username
.data
).first()
237 # Don't reveal success in case the lookup happened by email address.
238 success_message
=_("If that email address (case sensitive!) is "
239 "registered an email has been sent with instructions "
240 "on how to change your password.")
242 else: # found by username
243 user
= User
.query
.filter_by(
244 username
= fp_form
.username
.data
).first()
247 messages
.add_message(request
,
249 _("Couldn't find someone with that username."))
250 return redirect(request
, 'mediagoblin.auth.forgot_password')
252 success_message
=_("An email has been sent with instructions "
253 "on how to change your password.")
255 if user
and not(user
.email_verified
and user
.status
== 'active'):
256 # Don't send reminder because user is inactive or has no verified email
257 messages
.add_message(request
,
259 _("Could not send password recovery email as your username is in"
260 "active or your account's email address has not been verified."))
262 return redirect(request
, 'mediagoblin.user_pages.user_home',
265 # SUCCESS. Send reminder and return to login page
267 user
.fp_verification_key
= unicode(uuid
.uuid4())
268 user
.fp_token_expire
= datetime
.datetime
.now() + \
269 datetime
.timedelta(days
=10)
272 email_debug_message(request
)
273 send_fp_verification_email(user
, request
)
275 messages
.add_message(request
, messages
.INFO
, success_message
)
276 return redirect(request
, 'mediagoblin.auth.login')
279 def verify_forgot_password(request
):
281 Check the forgot-password verification and possibly let the user
282 change their password because of it.
284 # get form data variables, and specifically check for presence of token
285 formdata
= _process_for_token(request
)
286 if not formdata
['has_userid_and_token']:
287 return render_404(request
)
289 formdata_token
= formdata
['vars']['token']
290 formdata_userid
= formdata
['vars']['userid']
291 formdata_vars
= formdata
['vars']
293 # check if it's a valid user id
294 user
= User
.query
.filter_by(id=formdata_userid
).first()
296 return render_404(request
)
298 # check if we have a real user and correct token
299 if ((user
and user
.fp_verification_key
and
300 user
.fp_verification_key
== unicode(formdata_token
) and
301 datetime
.datetime
.now() < user
.fp_token_expire
302 and user
.email_verified
and user
.status
== 'active')):
304 cp_form
= auth_forms
.ChangePassForm(formdata_vars
)
306 if request
.method
== 'POST' and cp_form
.validate():
307 user
.pw_hash
= auth
.gen_password_hash(
308 cp_form
.password
.data
)
309 user
.fp_verification_key
= None
310 user
.fp_token_expire
= None
313 messages
.add_message(
316 _("You can now log in using your new password."))
317 return redirect(request
, 'mediagoblin.auth.login')
319 return render_to_response(
321 'mediagoblin/auth/change_fp.html',
323 'focus': 'password'})
325 # in case there is a valid id but no user with that id in the db
326 # or the token expired
328 return render_404(request
)
331 def _process_for_token(request
):
333 Checks for tokens in formdata without prior knowledge of request method
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?
338 # retrieve the formdata variables
339 if request
.method
== 'GET':
340 formdata_vars
= request
.GET
342 formdata_vars
= request
.form
345 'vars': formdata_vars
,
346 'has_userid_and_token':
347 'userid' in formdata_vars
and 'token' in formdata_vars
}