7ee89dfb5fa6eab8179918ac0b6418301dcf02c8
[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 if request.method == 'GET':
226 # If we don't have userid and token parameters, we can't do anything;404
227 if (not request.GET.has_key('userid') or
228 not request.GET.has_key('token')):
229 return exc.HTTPNotFound('You must provide userid and token')
230
231 # check if it's a valid Id
232 try:
233 user = request.db.User.find_one(
234 {'_id': ObjectId(unicode(request.GET['userid']))})
235 except InvalidId:
236 return exc.HTTPNotFound('Invalid id')
237
238 # check if we have a real user and correct token
239 if (user and
240 user['fp_verification_key'] == unicode(request.GET['token'])):
241 cp_form = auth_forms.ChangePassForm(request.GET)
242
243 return render_to_response(
244 request,
245 'mediagoblin/auth/change_fp.html',
246 {'cp_form': cp_form})
247 # in case there is a valid id but no user whit that id in the db
248 else:
249 return exc.HTTPNotFound('User not found')
250 if request.method == 'POST':
251 # verification doing here to prevent POST values modification
252 try:
253 user = request.db.User.find_one(
254 {'_id': ObjectId(unicode(request.POST['userid']))})
255 except InvalidId:
256 return exc.HTTPNotFound('Invalid id')
257
258 cp_form = auth_forms.ChangePassForm(request.POST)
259
260 # verification doing here to prevent POST values modification
261 # if token and id are correct they are able to change their password
262 if (user and
263 user['fp_verification_key'] == unicode(request.POST['token'])):
264
265 if cp_form.validate():
266 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
267 request.POST['password'])
268 user['fp_verification_key'] = None
269 user.save()
270
271 return redirect(request,
272 'mediagoblin.auth.fp_changed_success')
273 else:
274 return render_to_response(
275 request,
276 'mediagoblin/auth/change_fp.html',
277 {'cp_form': cp_form})
278 else:
279 return exc.HTTPNotFound('User not found')