views.py 14 KB

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