PageRenderTime 174ms CodeModel.GetById 30ms app.highlight 46ms RepoModel.GetById 55ms app.codeStats 0ms

/kai/controllers/consumer.py

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