refactors verify_forgot_password
[mediagoblin.git] / mediagoblin / auth / views.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
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
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.util import render_to_response, redirect, render_404
25 from mediagoblin.util 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 register(request):
34 """
35 Your classic registration view!
36 """
37 # Redirects to indexpage if registrations are disabled
38 if not mg_globals.app_config["allow_registration"]:
39 messages.add_message(
40 request,
41 messages.WARNING,
42 _('Sorry, registration is disabled on this instance.'))
43 return redirect(request, "index")
44
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
49
50 users_with_username = request.db.User.find(
51 {'username': request.POST['username'].lower()}).count()
52 users_with_email = request.db.User.find(
53 {'email': request.POST['email'].lower()}).count()
54
55 extra_validation_passes = True
56
57 if users_with_username:
58 register_form.username.errors.append(
59 _(u'Sorry, a user with that name already exists.'))
60 extra_validation_passes = False
61 if users_with_email:
62 register_form.email.errors.append(
63 _(u'Sorry, that email address has already been taken.'))
64 extra_validation_passes = False
65
66 if extra_validation_passes:
67 # Create the user
68 user = request.db.User()
69 user['username'] = request.POST['username'].lower()
70 user['email'] = request.POST['email'].lower()
71 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
72 request.POST['password'])
73 user.save(validate=True)
74
75 # log the user in
76 request.session['user_id'] = unicode(user['_id'])
77 request.session.save()
78
79 # send verification email
80 send_verification_email(user, request)
81
82 # redirect the user to their homepage... there will be a
83 # message waiting for them to verify their email
84 return redirect(
85 request, 'mediagoblin.user_pages.user_home',
86 user=user['username'])
87
88 return render_to_response(
89 request,
90 'mediagoblin/auth/register.html',
91 {'register_form': register_form})
92
93
94 def login(request):
95 """
96 MediaGoblin login view.
97
98 If you provide the POST with 'next', it'll redirect to that view.
99 """
100 login_form = auth_forms.LoginForm(request.POST)
101
102 login_failed = False
103
104 if request.method == 'POST' and login_form.validate():
105 user = request.db.User.one(
106 {'username': request.POST['username'].lower()})
107
108 if user and user.check_login(request.POST['password']):
109 # set up login in session
110 request.session['user_id'] = unicode(user['_id'])
111 request.session.save()
112
113 if request.POST.get('next'):
114 return exc.HTTPFound(location=request.POST['next'])
115 else:
116 return redirect(request, "index")
117
118 else:
119 # Prevent detecting who's on this system by testing login
120 # attempt timings
121 auth_lib.fake_login_attempt()
122 login_failed = True
123
124 return render_to_response(
125 request,
126 'mediagoblin/auth/login.html',
127 {'login_form': login_form,
128 'next': request.GET.get('next') or request.POST.get('next'),
129 'login_failed': login_failed,
130 'allow_registration': mg_globals.app_config["allow_registration"]})
131
132
133 def logout(request):
134 # Maybe deleting the user_id parameter would be enough?
135 request.session.delete()
136
137 return redirect(request, "index")
138
139
140 def verify_email(request):
141 """
142 Email verification view
143
144 validates GET parameters against database and unlocks the user account, if
145 you are lucky :)
146 """
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'):
149 return render_404(request)
150
151 user = request.db.User.find_one(
152 {'_id': ObjectId(unicode(request.GET['userid']))})
153
154 if user and user['verification_key'] == unicode(request.GET['token']):
155 user['status'] = u'active'
156 user['email_verified'] = True
157 user.save()
158 messages.add_message(
159 request,
160 messages.SUCCESS,
161 _("Your email address has been verified. "
162 "You may now login, edit your profile, and submit images!"))
163 else:
164 messages.add_message(
165 request,
166 messages.ERROR,
167 _('The verification key or user id is incorrect'))
168
169 return redirect(
170 request, 'mediagoblin.user_pages.user_home',
171 user=user['username'])
172
173
174 def resend_activation(request):
175 """
176 The reactivation view
177
178 Resend the activation email.
179 """
180 request.user['verification_key'] = unicode(uuid.uuid4())
181 request.user.save()
182
183 send_verification_email(request.user, request)
184
185 messages.add_message(
186 request,
187 messages.INFO,
188 _('Resent your verification email.'))
189 return redirect(
190 request, 'mediagoblin.user_pages.user_home',
191 user=request.user['username'])
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():
203 user = request.db.User.one(
204 {'$or': [{'username': request.POST['username']},
205 {'email': request.POST['username']}]})
206
207 if user:
208 user['fp_verification_key'] = unicode(uuid.uuid4())
209 user['fp_token_expire'] = datetime.datetime.now() + \
210 datetime.timedelta(days=10)
211 user.save()
212
213 send_fp_verification_email(user, request)
214
215 # do not reveal whether or not there is a matching user, just move along
216 return redirect(request, 'mediagoblin.auth.fp_email_sent')
217
218 return render_to_response(
219 request,
220 'mediagoblin/auth/forgot_password.html',
221 {'fp_form': fp_form})
222
223
224 def verify_forgot_password(request):
225 # get session variables, and specifically check for presence of token
226 mysession = _process_for_token(request)
227 if not mysession['token_complete']:
228 return render_404(request)
229
230 session_token = mysession['vars']['token']
231 session_userid = mysession['vars']['userid']
232 session_vars = mysession['vars']
233
234 # check if it's a valid Id
235 try:
236 user = request.db.User.find_one(
237 {'_id': ObjectId(unicode(session_userid))})
238 except InvalidId:
239 return render_404(request)
240
241 # check if we have a real user and correct token
242 if (user and user['fp_verification_key'] == unicode(session_token) and
243 datetime.datetime.now() < user['fp_token_expire']):
244 cp_form = auth_forms.ChangePassForm(session_vars)
245
246 if request.method == 'POST' and cp_form.validate():
247 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
248 request.POST['password'])
249 user['fp_verification_key'] = None
250 user['fp_token_expire'] = None
251 user.save()
252
253 return redirect(request, 'mediagoblin.auth.fp_changed_success')
254 else:
255 return render_to_response(request,
256 'mediagoblin/auth/change_fp.html',
257 {'cp_form': cp_form})
258 # in case there is a valid id but no user whit that id in the db
259 # or the token expired
260 else:
261 return render_404(request)
262
263
264 def _process_for_token(request):
265 """
266 Checks for tokens in session without prior knowledge of request method
267
268 For now, returns whether the userid and token session variables exist, and
269 the session variables in a hash. Perhaps an object is warranted?
270 """
271 # retrieve the session variables
272 if request.method == 'GET':
273 session_vars = request.GET
274 else:
275 session_vars = request.POST
276
277 mysession = {'vars': session_vars,
278 'token_complete': session_vars.has_key('userid') and
279 session_vars.has_key('token')}
280 return mysession