/kai/controllers/consumer.py

https://bitbucket.org/bbangert/kai/ · Python · 171 lines · 152 code · 17 blank · 2 comment · 12 complexity · 320eaeb2cb6247132e91845493c0db24 MD5 · raw file

  1. import logging
  2. from openid import sreg
  3. from openid.consumer import consumer
  4. from pylons import app_globals, cache, request, response, session, tmpl_context as c, url
  5. from pylons.controllers.util import abort, redirect
  6. from pylons.decorators import rest, secure, jsonify
  7. from kai.lib.base import BaseController, render
  8. from kai.lib.decorators import validate
  9. from kai.lib.helpers import failure_flash, success_flash
  10. from kai.model import Human, forms
  11. log = logging.getLogger(__name__)
  12. def proper_abort(end_action):
  13. if end_action == 'login':
  14. redirect(url('account_login'))
  15. else:
  16. redirect(url('account_register'))
  17. class ConsumerController(BaseController):
  18. def __before__(self):
  19. c.active_tab = True
  20. c.active_sub = True
  21. self.my_cache = cache.get_cache('ConsumerController.openid_session')
  22. self.openid_session = self.my_cache.get_value(
  23. key=session.id, createfunc=lambda: {}, expiretime=300)
  24. @rest.dispatch_on(POST='_handle_create')
  25. def create(self):
  26. return render('/accounts/register.mako')
  27. @validate(form=forms.openid_login_form, error_handler='create')
  28. def _handle_create(self):
  29. return self._openid_login('register')
  30. @rest.dispatch_on(POST='_handle_login')
  31. def login(self):
  32. return render('/accounts/login.mako')
  33. @validate(form=forms.openid_login_form, error_handler='login')
  34. @secure.authenticate_form
  35. def _handle_login(self):
  36. return self._openid_login()
  37. def _openid_login(self, end_action='login'):
  38. openid_url = self.form_result['openid_identifier']
  39. if not openid_url:
  40. redirect(url('account_login'))
  41. oidconsumer = consumer.Consumer(self.openid_session, app_globals.openid_store)
  42. try:
  43. authrequest = oidconsumer.begin(openid_url)
  44. except consumer.DiscoveryFailure, exc:
  45. err_msg = str(exc[0])
  46. if 'No usable OpenID' in err_msg:
  47. failure_flash('Invalid OpenID URL')
  48. else:
  49. failure_flash('Timeout for OpenID URL')
  50. proper_abort(end_action)
  51. if authrequest is None:
  52. failure_flash('No OpenID services found for <code>%s</code>' % openid_url)
  53. proper_abort(end_action)
  54. if end_action == 'register':
  55. sreg_request = sreg.SRegRequest(
  56. required=['fullname', 'email', 'timezone'],
  57. optional=['language']
  58. )
  59. authrequest.addExtension(sreg_request)
  60. session['openid_action'] = 'register'
  61. elif 'openid_action' in session:
  62. del session['openid_action']
  63. trust_root = url('home', qualified=True)
  64. return_to = url('openid_process', qualified=True)
  65. # OpenID 2.0 lets Providers request POST instead of redirect, this
  66. # checks for such a request.
  67. if authrequest.shouldSendRedirect():
  68. redirect_url = authrequest.redirectURL(realm=trust_root,
  69. return_to=return_to,
  70. immediate=False)
  71. self.my_cache.set_value(key=session.id, value=self.openid_session, expiretime=300)
  72. redirect(url(redirect_url))
  73. else:
  74. form_html = authrequest.formMarkup(
  75. realm=trust_root, return_to=return_to, immediate=False,
  76. form_tag_attrs={'id':'openid_message'})
  77. self.my_cache.set_value(key=session.id, value=self.openid_session, expiretime=300)
  78. return form_html
  79. def process(self):
  80. """Handle incoming redirect from OpenID Provider"""
  81. end_action = session.get('openid_action', 'login')
  82. oidconsumer = consumer.Consumer(self.openid_session, app_globals.openid_store)
  83. info = oidconsumer.complete(request.params, url('openid_process', qualified=True))
  84. if info.status == consumer.FAILURE and info.identity_url:
  85. fmt = "Verification of %s failed: %s"
  86. failure_flash(fmt % (info.identity_url, info.message))
  87. elif info.status == consumer.SUCCESS:
  88. openid_identity = info.identity_url
  89. if info.endpoint.canonicalID:
  90. # If it's an i-name, use the canonicalID as its secure even if
  91. # the old one is compromised
  92. openid_identity = info.endpoint.canonicalID
  93. # We've now verified a successful OpenID login, time to determine
  94. # how to dispatch it
  95. # First save their identity in the session, as several pages use
  96. # this data
  97. session['openid_identity'] = openid_identity
  98. # Save off the session
  99. session.save()
  100. # First, if someone already has this openid, we log them in if
  101. # they've verified their email, otherwise we inform them to verify
  102. # their email address first
  103. users = list(Human.by_openid(self.db)[openid_identity])
  104. if users:
  105. user = users[0]
  106. if user.email_token:
  107. failure_flash('You must verify your email before signing in.')
  108. redirect(url('account_login'))
  109. else:
  110. user.process_login()
  111. success_flash('You have logged into PylonsHQ')
  112. if session.get('redirect'):
  113. redir_url = session.pop('redirect')
  114. session.save()
  115. redirect(url(redir_url))
  116. redirect(url('home'))
  117. # Second, if this is a registration request, present the rest of
  118. # registration process
  119. if session.get('openid_action', '') == 'register':
  120. sreg_response = sreg.SRegResponse.fromSuccessResponse(info)
  121. results = {}
  122. # Just in case the user didn't provide sreg details
  123. if sreg_response:
  124. results['displayname'] = sreg_response.get('fullname')
  125. results['timezone'] = sreg_response.get('timezone')
  126. results['email_address'] = sreg_response.get('email')
  127. c.defaults = results
  128. c.openid = openid_identity
  129. return render('/accounts/register.mako')
  130. # The last option possible, is that the user is associating this
  131. # OpenID account with an existing account
  132. c.openid = openid_identity
  133. return render('/accounts/associate.mako')
  134. elif info.status == consumer.CANCEL:
  135. failure_flash('Verification cancelled')
  136. elif info.status == consumer.SETUP_NEEDED:
  137. if info.setup_url:
  138. c.message = '<a href=%s>Setup needed</a>' % info.setup_url
  139. else:
  140. # This means auth didn't succeed, but you're welcome to try
  141. # non-immediate mode.
  142. failure_flash('Setup needed')
  143. else:
  144. failure_flash('Verification failed.')
  145. session.delete()
  146. proper_abort(end_action)