Merge branch 'master' into OPW-Moderation-Update
[mediagoblin.git] / mediagoblin / plugins / openid / views.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 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 from openid.consumer import consumer
17 from openid.consumer.discover import DiscoveryFailure
18 from openid.extensions.sreg import SRegRequest, SRegResponse
19
20 from mediagoblin import mg_globals, messages
21 from mediagoblin.db.models import User
22 from mediagoblin.decorators import (auth_enabled, allow_registration,
23 require_active_login)
24 from mediagoblin.tools.response import redirect, render_to_response
25 from mediagoblin.tools.translate import pass_to_ugettext as _
26 from mediagoblin.plugins.openid import forms as auth_forms
27 from mediagoblin.plugins.openid.models import OpenIDUserURL
28 from mediagoblin.plugins.openid.store import SQLAlchemyOpenIDStore
29 from mediagoblin.auth.tools import register_user
30
31
32 def _start_verification(request, form, return_to, sreg=True):
33 """
34 Start OpenID Verification.
35
36 Returns False if verification fails, otherwise, will return either a
37 redirect or render_to_response object
38 """
39 openid_url = form.openid.data
40 c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
41
42 # Try to discover provider
43 try:
44 auth_request = c.begin(openid_url)
45 except DiscoveryFailure:
46 # Discovery failed, return to login page
47 form.openid.errors.append(
48 _('Sorry, the OpenID server could not be found'))
49
50 return False
51
52 host = 'http://' + request.host
53
54 if sreg:
55 # Ask provider for email and nickname
56 auth_request.addExtension(SRegRequest(required=['email', 'nickname']))
57
58 # Do we even need this?
59 if auth_request is None:
60 form.openid.errors.append(
61 _('No OpenID service was found for %s' % openid_url))
62
63 elif auth_request.shouldSendRedirect():
64 # Begin the authentication process as a HTTP redirect
65 redirect_url = auth_request.redirectURL(
66 host, return_to)
67
68 return redirect(
69 request, location=redirect_url)
70
71 else:
72 # Send request as POST
73 form_html = auth_request.htmlMarkup(
74 host, host + return_to,
75 # Is this necessary?
76 form_tag_attrs={'id': 'openid_message'})
77
78 # Beware: this renders a template whose content is a form
79 # and some javascript to submit it upon page load. Non-JS
80 # users will have to click the form submit button to
81 # initiate OpenID authentication.
82 return render_to_response(
83 request,
84 'mediagoblin/plugins/openid/request_form.html',
85 {'html': form_html})
86
87 return False
88
89
90 def _finish_verification(request):
91 """
92 Complete OpenID Verification Process.
93
94 If the verification failed, will return false, otherwise, will return
95 the response
96 """
97 c = consumer.Consumer(request.session, SQLAlchemyOpenIDStore())
98
99 # Check the response from the provider
100 response = c.complete(request.args, request.base_url)
101 if response.status == consumer.FAILURE:
102 messages.add_message(
103 request,
104 messages.WARNING,
105 _('Verification of %s failed: %s' %
106 (response.getDisplayIdentifier(), response.message)))
107
108 elif response.status == consumer.SUCCESS:
109 # Verification was successfull
110 return response
111
112 elif response.status == consumer.CANCEL:
113 # Verification canceled
114 messages.add_message(
115 request,
116 messages.WARNING,
117 _('Verification cancelled'))
118
119 return False
120
121
122 def _response_email(response):
123 """ Gets the email from the OpenID providers response"""
124 sreg_response = SRegResponse.fromSuccessResponse(response)
125 if sreg_response and 'email' in sreg_response:
126 return sreg_response.data['email']
127 return None
128
129
130 def _response_nickname(response):
131 """ Gets the nickname from the OpenID providers response"""
132 sreg_response = SRegResponse.fromSuccessResponse(response)
133 if sreg_response and 'nickname' in sreg_response:
134 return sreg_response.data['nickname']
135 return None
136
137
138 @auth_enabled
139 def login(request):
140 """OpenID Login View"""
141 login_form = auth_forms.LoginForm(request.form)
142 allow_registration = mg_globals.app_config["allow_registration"]
143
144 # Can't store next in request.GET because of redirects to OpenID provider
145 # Store it in the session
146 next = request.GET.get('next')
147 request.session['next'] = next
148
149 login_failed = False
150
151 if request.method == 'POST' and login_form.validate():
152 return_to = request.urlgen(
153 'mediagoblin.plugins.openid.finish_login')
154
155 success = _start_verification(request, login_form, return_to)
156
157 if success:
158 return success
159
160 login_failed = True
161
162 return render_to_response(
163 request,
164 'mediagoblin/plugins/openid/login.html',
165 {'login_form': login_form,
166 'next': request.session.get('next'),
167 'login_failed': login_failed,
168 'post_url': request.urlgen('mediagoblin.plugins.openid.login'),
169 'allow_registration': allow_registration})
170
171
172 @auth_enabled
173 def finish_login(request):
174 """Complete OpenID Login Process"""
175 response = _finish_verification(request)
176
177 if not response:
178 # Verification failed, redirect to login page.
179 return redirect(request, 'mediagoblin.plugins.openid.login')
180
181 # Verification was successfull
182 query = OpenIDUserURL.query.filter_by(
183 openid_url=response.identity_url,
184 ).first()
185 user = query.user if query else None
186
187 if user:
188 # Set up login in session
189 request.session['user_id'] = unicode(user.id)
190 request.session.save()
191
192 if request.session.get('next'):
193 return redirect(request, location=request.session.pop('next'))
194 else:
195 return redirect(request, "index")
196 else:
197 # No user, need to register
198 if not mg_globals.app.auth:
199 messages.add_message(
200 request,
201 messages.WARNING,
202 _('Sorry, authentication is disabled on this instance.'))
203 return redirect(request, 'index')
204
205 # Get email and nickname from response
206 email = _response_email(response)
207 username = _response_nickname(response)
208
209 register_form = auth_forms.RegistrationForm(request.form,
210 openid=response.identity_url,
211 email=email,
212 username=username)
213 return render_to_response(
214 request,
215 'mediagoblin/auth/register.html',
216 {'register_form': register_form,
217 'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
218
219
220 @allow_registration
221 @auth_enabled
222 def register(request):
223 """OpenID Registration View"""
224 if request.method == 'GET':
225 # Need to connect to openid provider before registering a user to
226 # get the users openid url. If method is 'GET', then this page was
227 # acessed without logging in first.
228 return redirect(request, 'mediagoblin.plugins.openid.login')
229
230 register_form = auth_forms.RegistrationForm(request.form)
231
232 if register_form.validate():
233 user = register_user(request, register_form)
234
235 if user:
236 # redirect the user to their homepage... there will be a
237 # message waiting for them to verify their email
238 return redirect(
239 request, 'mediagoblin.user_pages.user_home',
240 user=user.username)
241
242 return render_to_response(
243 request,
244 'mediagoblin/auth/register.html',
245 {'register_form': register_form,
246 'post_url': request.urlgen('mediagoblin.plugins.openid.register')})
247
248
249 @require_active_login
250 def start_edit(request):
251 """Starts the process of adding an openid url to a users account"""
252 form = auth_forms.LoginForm(request.form)
253
254 if request.method == 'POST' and form.validate():
255 query = OpenIDUserURL.query.filter_by(
256 openid_url=form.openid.data
257 ).first()
258 user = query.user if query else None
259
260 if not user:
261 return_to = request.urlgen('mediagoblin.plugins.openid.finish_edit')
262 success = _start_verification(request, form, return_to, False)
263
264 if success:
265 return success
266 else:
267 form.openid.errors.append(
268 _('Sorry, an account is already registered to that OpenID.'))
269
270 return render_to_response(
271 request,
272 'mediagoblin/plugins/openid/add.html',
273 {'form': form,
274 'post_url': request.urlgen('mediagoblin.plugins.openid.edit')})
275
276
277 @require_active_login
278 def finish_edit(request):
279 """Finishes the process of adding an openid url to a user"""
280 response = _finish_verification(request)
281
282 if not response:
283 # Verification failed, redirect to add openid page.
284 return redirect(request, 'mediagoblin.plugins.openid.edit')
285
286 # Verification was successfull
287 query = OpenIDUserURL.query.filter_by(
288 openid_url=response.identity_url,
289 ).first()
290 user_exists = query.user if query else None
291
292 if user_exists:
293 # user exists with that openid url, redirect back to edit page
294 messages.add_message(
295 request,
296 messages.WARNING,
297 _('Sorry, an account is already registered to that OpenID.'))
298 return redirect(request, 'mediagoblin.plugins.openid.edit')
299
300 else:
301 # Save openid to user
302 user = User.query.filter_by(
303 id=request.session['user_id']
304 ).first()
305
306 new_entry = OpenIDUserURL()
307 new_entry.openid_url = response.identity_url
308 new_entry.user_id = user.id
309 new_entry.save()
310
311 messages.add_message(
312 request,
313 messages.SUCCESS,
314 _('Your OpenID url was saved successfully.'))
315
316 return redirect(request, 'mediagoblin.edit.account')
317
318
319 @require_active_login
320 def delete_openid(request):
321 """View to remove an openid from a users account"""
322 form = auth_forms.LoginForm(request.form)
323
324 if request.method == 'POST' and form.validate():
325 # Check if a user has this openid
326 query = OpenIDUserURL.query.filter_by(
327 openid_url=form.openid.data
328 )
329 user = query.first().user if query.first() else None
330
331 if user and user.id == int(request.session['user_id']):
332 count = len(user.openid_urls)
333 if not count > 1 and not user.pw_hash:
334 # Make sure the user has a pw or another OpenID
335 messages.add_message(
336 request,
337 messages.WARNING,
338 _("You can't delete your only OpenID URL unless you"
339 " have a password set"))
340 elif user:
341 # There is a user, but not the same user who is logged in
342 form.openid.errors.append(
343 _('That OpenID is not registered to this account.'))
344
345 if not form.errors and not request.session.get('messages'):
346 # Okay to continue with deleting openid
347 return_to = request.urlgen(
348 'mediagoblin.plugins.openid.finish_delete')
349 success = _start_verification(request, form, return_to, False)
350
351 if success:
352 return success
353
354 return render_to_response(
355 request,
356 'mediagoblin/plugins/openid/delete.html',
357 {'form': form,
358 'post_url': request.urlgen('mediagoblin.plugins.openid.delete')})
359
360
361 @require_active_login
362 def finish_delete(request):
363 """Finishes the deletion of an OpenID from an user's account"""
364 response = _finish_verification(request)
365
366 if not response:
367 # Verification failed, redirect to delete openid page.
368 return redirect(request, 'mediagoblin.plugins.openid.delete')
369
370 query = OpenIDUserURL.query.filter_by(
371 openid_url=response.identity_url
372 )
373 user = query.first().user if query.first() else None
374
375 # Need to check this again because of generic openid urls such as google's
376 if user and user.id == int(request.session['user_id']):
377 count = len(user.openid_urls)
378 if count > 1 or user.pw_hash:
379 # User has more then one openid or also has a password.
380 query.first().delete()
381
382 messages.add_message(
383 request,
384 messages.SUCCESS,
385 _('OpenID was successfully removed.'))
386
387 return redirect(request, 'mediagoblin.edit.account')
388
389 elif not count > 1:
390 messages.add_message(
391 request,
392 messages.WARNING,
393 _("You can't delete your only OpenID URL unless you have a "
394 "password set"))
395
396 return redirect(request, 'mediagoblin.plugins.openid.delete')
397
398 else:
399 messages.add_message(
400 request,
401 messages.WARNING,
402 _('That OpenID is not registered to this account.'))
403
404 return redirect(request, 'mediagoblin.plugins.openid.delete')