Back to index

moin  1.9.0~rc2
openidrp.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - OpenID authorization
00004 
00005     @copyright: 2007 MoinMoin:JohannesBerg
00006     @license: GNU GPL, see COPYING for details.
00007 """
00008 from MoinMoin import log
00009 logging = log.getLogger(__name__)
00010 
00011 from MoinMoin.util.moinoid import MoinOpenIDStore
00012 from MoinMoin import user
00013 from MoinMoin.auth import BaseAuth
00014 from openid.consumer import consumer
00015 from openid.yadis.discover import DiscoveryFailure
00016 from openid.fetchers import HTTPFetchingError
00017 from MoinMoin.widget import html
00018 from MoinMoin.auth import CancelLogin, ContinueLogin
00019 from MoinMoin.auth import MultistageFormLogin, MultistageRedirectLogin
00020 from MoinMoin.auth import get_multistage_continuation_url
00021 from werkzeug.utils import url_encode
00022 
00023 class OpenIDAuth(BaseAuth):
00024     login_inputs = ['openid_identifier']
00025     name = 'openid'
00026     logout_possible = True
00027     auth_attribs = ()
00028 
00029     def __init__(self, modify_request=None,
00030                        update_user=None,
00031                        create_user=None,
00032                        forced_service=None,
00033                        idselector_com=None):
00034         BaseAuth.__init__(self)
00035         self._modify_request = modify_request or (lambda x, c: None)
00036         self._update_user = update_user or (lambda i, u, c: None)
00037         self._create_user = create_user or (lambda i, u, c: None)
00038         self._forced_service = forced_service
00039         self._idselector_com = idselector_com
00040         if forced_service:
00041             self.login_inputs = ['special_no_input']
00042 
00043     def _handle_user_data(self, request, u):
00044         create = not u
00045         if create:
00046             # pass in a created but unsaved user object
00047             u = user.User(request, auth_method=self.name,
00048                           auth_username=request.session['openid.id'],
00049                           auth_attribs=self.auth_attribs)
00050             # invalid name
00051             u.name = ''
00052             u = self._create_user(request.session['openid.info'], u, request.cfg)
00053 
00054         if u:
00055             self._update_user(request.session['openid.info'], u, request.cfg)
00056 
00057             # just in case the wiki admin screwed up
00058             if (not user.isValidName(request, u.name) or
00059                 (create and user.getUserId(request, u.name))):
00060                 return None
00061 
00062             if not hasattr(u, 'openids'):
00063                 u.openids = []
00064             if not request.session['openid.id'] in u.openids:
00065                 u.openids.append(request.session['openid.id'])
00066 
00067             u.save()
00068 
00069             del request.session['openid.id']
00070             del request.session['openid.info']
00071 
00072         return u
00073 
00074     def _get_account_name(self, request, form, msg=None):
00075         # now we need to ask the user for a new username
00076         # that they want to use on this wiki
00077         # XXX: request nickname from OP and suggest using it
00078         # (if it isn't in use yet)
00079         logging.debug("running _get_account_name")
00080         _ = request.getText
00081         form.append(html.INPUT(type='hidden', name='oidstage', value='2'))
00082         table = html.TABLE(border='0')
00083         form.append(table)
00084         td = html.TD(colspan=2)
00085         td.append(html.Raw(_("""Please choose an account name now.
00086 If you choose an existing account name you will be asked for the
00087 password and be able to associate the account with your OpenID.""")))
00088         table.append(html.TR().append(td))
00089         if msg:
00090             td = html.TD(colspan='2')
00091             td.append(html.P().append(html.STRONG().append(html.Raw(msg))))
00092             table.append(html.TR().append(td))
00093         td1 = html.TD()
00094         td1.append(html.STRONG().append(html.Raw(_('Name'))))
00095         td2 = html.TD()
00096         td2.append(html.INPUT(type='text', name='username'))
00097         table.append(html.TR().append(td1).append(td2))
00098         td1 = html.TD()
00099         td2 = html.TD()
00100         td2.append(html.INPUT(type='submit', name='submit',
00101                               value=_('Choose this name')))
00102         table.append(html.TR().append(td1).append(td2))
00103 
00104     def _get_account_name_inval_user(self, request, form):
00105         _ = request.getText
00106         msg = _('This is not a valid username, choose a different one.')
00107         return self._get_account_name(request, form, msg=msg)
00108 
00109     def _associate_account(self, request, form, accountname, msg=None):
00110         _ = request.getText
00111 
00112         form.append(html.INPUT(type='hidden', name='oidstage', value='3'))
00113         table = html.TABLE(border='0')
00114         form.append(table)
00115         td = html.TD(colspan=2)
00116         td.append(html.Raw(_("""The username you have chosen is already
00117 taken. If it is your username, enter your password below to associate
00118 the username with your OpenID. Otherwise, please choose a different
00119 username and leave the password field blank.""")))
00120         table.append(html.TR().append(td))
00121         if msg:
00122             td.append(html.P().append(html.STRONG().append(html.Raw(msg))))
00123         td1 = html.TD()
00124         td1.append(html.STRONG().append(html.Raw(_('Name'))))
00125         td2 = html.TD()
00126         td2.append(html.INPUT(type='text', name='username', value=accountname))
00127         table.append(html.TR().append(td1).append(td2))
00128         td1 = html.TD()
00129         td1.append(html.STRONG().append(html.Raw(_('Password'))))
00130         td2 = html.TD()
00131         td2.append(html.INPUT(type='password', name='password'))
00132         table.append(html.TR().append(td1).append(td2))
00133         td1 = html.TD()
00134         td2 = html.TD()
00135         td2.append(html.INPUT(type='submit', name='submit',
00136                               value=_('Associate this name')))
00137         table.append(html.TR().append(td1).append(td2))
00138 
00139     def _handle_verify_continuation(self, request):
00140         _ = request.getText
00141         oidconsumer = consumer.Consumer(request.session,
00142                                         MoinOpenIDStore(request))
00143         query = {}
00144         for key in request.values.keys():
00145             query[key] = request.values.get(key)
00146         current_url = get_multistage_continuation_url(request, self.name,
00147                                                       {'oidstage': '1'})
00148         info = oidconsumer.complete(query, current_url)
00149         if info.status == consumer.FAILURE:
00150             logging.debug(_("OpenID error: %s.") % info.message)
00151             return CancelLogin(_('OpenID error: %s.') % info.message)
00152         elif info.status == consumer.CANCEL:
00153             logging.debug(_("OpenID verification canceled."))
00154             return CancelLogin(_('Verification canceled.'))
00155         elif info.status == consumer.SUCCESS:
00156             logging.debug(_("OpenID success. id: %s") % info.identity_url)
00157             request.session['openid.id'] = info.identity_url
00158             request.session['openid.info'] = info
00159 
00160             # try to find user object
00161             uid = user.getUserIdByOpenId(request, info.identity_url)
00162             if uid:
00163                 u = user.User(request, id=uid, auth_method=self.name,
00164                               auth_username=info.identity_url,
00165                               auth_attribs=self.auth_attribs)
00166             else:
00167                 u = None
00168 
00169             # create or update the user according to the registration data
00170             u = self._handle_user_data(request, u)
00171             if u:
00172                 return ContinueLogin(u)
00173 
00174             # if no user found, then we need to ask for a username,
00175             # possibly associating an existing account.
00176             logging.debug("OpenID: No user found, prompting for username")
00177             #request.session['openid.id'] = info.identity_url
00178             return MultistageFormLogin(self._get_account_name)
00179         else:
00180             logging.debug(_("OpenID failure"))
00181             return CancelLogin(_('OpenID failure.'))
00182 
00183     def _handle_name_continuation(self, request):
00184         if not 'openid.id' in request.session:
00185             return CancelLogin(_('No OpenID found in session.'))
00186 
00187         _ = request.getText
00188         newname = request.form.get('username', '')
00189         if not newname:
00190             return MultistageFormLogin(self._get_account_name)
00191         if not user.isValidName(request, newname):
00192             return MultistageFormLogin(self._get_account_name_inval_user)
00193         uid = None
00194         if newname:
00195             uid = user.getUserId(request, newname)
00196         if not uid:
00197             # we can create a new user with this name :)
00198             u = user.User(request, auth_method=self.name,
00199                           auth_username=request.session['openid.id'],
00200                           auth_attribs=self.auth_attribs)
00201             u.name = newname
00202             u = self._handle_user_data(request, u)
00203             return ContinueLogin(u)
00204         # requested username already exists. if they know the password,
00205         # they can associate that account with the openid.
00206         assoc = lambda req, form: self._associate_account(req, form, newname)
00207         return MultistageFormLogin(assoc)
00208 
00209     def _handle_associate_continuation(self, request):
00210         if not 'openid.id' in request.session:
00211             return CancelLogin(_('No OpenID found in session.'))
00212 
00213         _ = request.getText
00214         username = request.form.get('username', '')
00215         password = request.form.get('password', '')
00216         if not password:
00217             return self._handle_name_continuation(request)
00218         u = user.User(request, name=username, password=password,
00219                       auth_method=self.name,
00220                       auth_username=request.session['openid.id'],
00221                       auth_attribs=self.auth_attribs)
00222         if u.valid:
00223             self._handle_user_data(request, u)
00224             return ContinueLogin(u, _('Your account is now associated to your OpenID.'))
00225         else:
00226             msg = _('The password you entered is not valid.')
00227             assoc = lambda req, form: self._associate_account(req, form, username, msg=msg)
00228             return MultistageFormLogin(assoc)
00229 
00230     def _handle_continuation(self, request):
00231         _ = request.getText
00232         oidstage = request.values.get('oidstage')
00233         if oidstage == '1':
00234             logging.debug('OpenID: handle verify continuation')
00235             return self._handle_verify_continuation(request)
00236         elif oidstage == '2':
00237             logging.debug('OpenID: handle name continuation')
00238             return self._handle_name_continuation(request)
00239         elif oidstage == '3':
00240             logging.debug('OpenID: handle associate continuation')
00241             return self._handle_associate_continuation(request)
00242         logging.debug('OpenID error: unknown continuation stage')
00243         return CancelLogin(_('OpenID error: unknown continuation stage'))
00244 
00245     def _openid_form(self, request, form, oidhtml):
00246         _ = request.getText
00247         txt = _('OpenID verification requires that you click this button:')
00248         # create JS to automatically submit the form if possible
00249         submitjs = """<script type="text/javascript">
00250 <!--//
00251 document.getElementById("openid_message").submit();
00252 //-->
00253 </script>
00254 """
00255         return ''.join([txt, oidhtml, submitjs])
00256 
00257     def login(self, request, user_obj, **kw):
00258         continuation = kw.get('multistage')
00259 
00260         if continuation:
00261             return self._handle_continuation(request)
00262 
00263         # openid is designed to work together with other auths
00264         if user_obj and user_obj.valid:
00265             return ContinueLogin(user_obj)
00266 
00267         openid_id = kw.get('openid_identifier')
00268 
00269         # nothing entered? continue...
00270         if not self._forced_service and not openid_id:
00271             return ContinueLogin(user_obj)
00272 
00273         _ = request.getText
00274 
00275         # user entered something but the session can't be stored
00276         if not request.cfg.cookie_lifetime[0]:
00277             return ContinueLogin(user_obj,
00278                                  _('Anonymous sessions need to be enabled for OpenID login.'))
00279 
00280         oidconsumer = consumer.Consumer(request.session,
00281                                         MoinOpenIDStore(request))
00282 
00283         try:
00284             fserv = self._forced_service
00285             if fserv:
00286                 if isinstance(fserv, str) or isinstance(fserv, unicode):
00287                     oidreq = oidconsumer.begin(fserv)
00288                 else:
00289                     oidreq = oidconsumer.beginWithoutDiscovery(fserv)
00290             else:
00291                 oidreq = oidconsumer.begin(openid_id)
00292         except HTTPFetchingError:
00293             return ContinueLogin(None, _('Failed to resolve OpenID.'))
00294         except DiscoveryFailure:
00295             return ContinueLogin(None, _('OpenID discovery failure, not a valid OpenID.'))
00296         else:
00297             if oidreq is None:
00298                 return ContinueLogin(None, _('No OpenID.'))
00299 
00300             self._modify_request(oidreq, request.cfg)
00301 
00302             return_to = get_multistage_continuation_url(request, self.name,
00303                                                         {'oidstage': '1'})
00304             trust_root = request.url_root
00305             if oidreq.shouldSendRedirect():
00306                 redirect_url = oidreq.redirectURL(trust_root, return_to)
00307                 return MultistageRedirectLogin(redirect_url)
00308             else:
00309                 form_html = oidreq.formMarkup(trust_root, return_to,
00310                     form_tag_attrs={'id': 'openid_message'})
00311                 mcall = lambda request, form:\
00312                     self._openid_form(request, form, form_html)
00313                 ret = MultistageFormLogin(mcall)
00314                 return ret
00315 
00316     def login_hint(self, request):
00317         _ = request.getText
00318         msg = u''
00319         if self._idselector_com:
00320             msg = self._idselector_com
00321         msg += _("If you do not have an account yet, you can still log in "
00322                  "with your OpenID and create one during login.")
00323         return msg