Back to index

moin  1.9.0~rc2
serveopenid.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     MoinMoin - OpenID server action
00004 
00005     This is the UI and provider for OpenID.
00006 
00007     @copyright: 2006, 2007, 2008 Johannes Berg <johannes@sipsolutions.net>
00008     @license: GNU GPL, see COPYING for details.
00009 """
00010 
00011 from MoinMoin.support.python_compatibility import rsplit
00012 from MoinMoin.util.moinoid import MoinOpenIDStore, strbase64
00013 from MoinMoin import wikiutil
00014 from openid.consumer.discover import OPENID_1_0_TYPE, \
00015     OPENID_1_1_TYPE, OPENID_2_0_TYPE, OPENID_IDP_2_0_TYPE
00016 from openid import sreg
00017 from openid.cryptutil import randomString
00018 from openid.server import server
00019 from openid.message import IDENTIFIER_SELECT
00020 from MoinMoin.widget import html
00021 from MoinMoin.web.request import MoinMoinFinish
00022 
00023 def execute(pagename, request):
00024     return MoinOpenIDServer(pagename, request).handle()
00025 
00026 class MoinOpenIDServer:
00027     def __init__(self, pagename, request):
00028         self.request = request
00029         self._ = request.getText
00030         self.cfg = request.cfg
00031 
00032     def serveYadisEP(self, endpoint_url):
00033         request = self.request
00034         request.content_type = 'application/xrds+xml'
00035 
00036         user_url = request.getQualifiedURL(request.page.url(request))
00037         self.request.write("""\
00038 <?xml version="1.0" encoding="UTF-8"?>
00039 <xrds:XRDS
00040     xmlns:xrds="xri://$xrds"
00041     xmlns="xri://$xrd*($v*2.0)">
00042   <XRD>
00043 
00044     <Service priority="0">
00045       <Type>%(type10)s</Type>
00046       <URI>%(uri)s</URI>
00047       <LocalID>%(id)s</LocalID>
00048     </Service>
00049 
00050     <Service priority="0">
00051       <Type>%(type11)s</Type>
00052       <URI>%(uri)s</URI>
00053       <LocalID>%(id)s</LocalID>
00054     </Service>
00055 
00056     <!-- older version of the spec draft -->
00057     <Service priority="0">
00058       <Type>http://openid.net/signon/2.0</Type>
00059       <URI>%(uri)s</URI>
00060       <LocalID>%(id)s</LocalID>
00061     </Service>
00062 
00063     <Service priority="0">
00064       <Type>%(type20)s</Type>
00065       <URI>%(uri)s</URI>
00066       <LocalID>%(id)s</LocalID>
00067     </Service>
00068 
00069   </XRD>
00070 </xrds:XRDS>
00071 """ % {
00072     'type10': OPENID_1_0_TYPE,
00073     'type11': OPENID_1_1_TYPE,
00074     'type20': OPENID_2_0_TYPE,
00075     'uri': endpoint_url,
00076     'id': user_url
00077 })
00078 
00079     def serveYadisIDP(self, endpoint_url):
00080         request = self.request
00081         request.content_type = 'application/xrds+xml'
00082 
00083         user_url = request.getQualifiedURL(request.page.url(request))
00084         self.request.write("""\
00085 <?xml version="1.0" encoding="UTF-8"?>
00086 <xrds:XRDS
00087     xmlns:xrds="xri://$xrds"
00088     xmlns="xri://$xrd*($v*2.0)">
00089   <XRD>
00090 
00091     <Service priority="0">
00092       <Type>%(typeidp)s</Type>
00093       <URI>%(uri)s</URI>
00094       <LocalID>%(id)s</LocalID>
00095     </Service>
00096 
00097   </XRD>
00098 </xrds:XRDS>
00099 """ % {
00100     'typeidp': OPENID_IDP_2_0_TYPE,
00101     'uri': endpoint_url,
00102     'id': user_url
00103 })
00104 
00105     def _verify_endpoint_identity(self, identity):
00106         """
00107            Verify that the given identity matches the current endpoint.
00108 
00109            We always serve out /UserName?action=... for the UserName
00110            OpenID and this is pure paranoia to make sure it is that way
00111            on incoming data.
00112 
00113            Also verify that the given identity is allowed to have an OpenID.
00114         """
00115         request = self.request
00116         cfg = request.cfg
00117 
00118         # we can very well split on the last slash since usernames
00119         # must not contain slashes
00120         base, received_name = rsplit(identity, '/', 1)
00121         check_name = received_name
00122 
00123         if received_name == '':
00124             pg = wikiutil.getFrontPage(request)
00125             if pg:
00126                 received_name = pg.page_name
00127                 check_name = received_name
00128                 if 'openid.user' in pg.pi:
00129                     received_name = pg.pi['openid.user']
00130 
00131         # some sanity checking
00132         # even if someone goes to http://johannes.sipsolutions.net/
00133         # we'll serve out http://johannes.sipsolutions.net/JohannesBerg?action=serveopenid
00134         # (if JohannesBerg is set as page_front_page)
00135         # For the #OpenIDUser PI, we need to allow the page that includes the PI,
00136         # hence use check_name here (see above for how it is assigned)
00137         fullidentity = '/'.join([base, check_name])
00138         thisurl = request.getQualifiedURL(request.page.url(request))
00139         if not thisurl == fullidentity:
00140             return False
00141 
00142         # again, we never put an openid.server link on this page...
00143         # why are they here?
00144         openid_group_name = cfg.openid_server_restricted_users_group
00145         if openid_group_name and received_name not in request.groups.get(openid_group_name, []):
00146             return False
00147 
00148         return True
00149 
00150     def handleCheckIDRequest(self, identity, username, openidreq, server_url):
00151         if self.user_trusts_url(openidreq.trust_root):
00152             return self.approved(identity, openidreq, server_url=server_url)
00153 
00154         if openidreq.immediate:
00155             return openidreq.answer(False, identity=identity, server_url=server_url)
00156 
00157         self.request.session['openidserver.request'] = openidreq
00158         self.show_decide_page(identity, username, openidreq)
00159         return None
00160 
00161     def _make_identity(self):
00162         page = wikiutil.getHomePage(self.request)
00163         if page:
00164             server_url = self.request.getQualifiedURL(
00165                              page.url(self.request, querystr={'action': 'serveopenid'}))
00166             identity = self.request.getQualifiedURL(page.url(self.request))
00167             return identity, server_url
00168         return None, None
00169 
00170     def handle(self):
00171         _ = self._
00172         request = self.request
00173         form = request.form
00174 
00175         username = request.page.page_name
00176         if 'openid.user' in request.page.pi:
00177             username = request.page.pi['openid.user']
00178 
00179 
00180         if not request.cfg.openid_server_enabled:
00181             # since we didn't put any openid.server into
00182             # the page to start with, this is someone trying
00183             # to abuse us. No need to give a nice error
00184             request.makeForbidden(403, '')
00185             return
00186 
00187         server_url = request.getQualifiedURL(
00188                          request.page.url(request, querystr={'action': 'serveopenid'}))
00189 
00190         yadis_type = form.get('yadis')
00191         if yadis_type == 'ep':
00192             return self.serveYadisEP(server_url)
00193         elif yadis_type == 'idp':
00194             return self.serveYadisIDP(server_url)
00195 
00196         # if the identity is set it must match the server URL
00197         # sort of arbitrary, but we have to have some restriction
00198         identity = form.get('openid.identity')
00199         if identity == IDENTIFIER_SELECT:
00200             identity, server_url = self._make_identity()
00201             if not identity:
00202                 return self._sorry_no_identity()
00203             username = request.user.name
00204         elif identity is not None:
00205             if not self._verify_endpoint_identity(identity):
00206                 request.makeForbidden(403, 'verification failed')
00207                 return
00208 
00209         if 'openid.user' in request.page.pi:
00210             username = request.page.pi['openid.user']
00211 
00212         store = MoinOpenIDStore(request)
00213         openidsrv = server.Server(store, op_endpoint=server_url)
00214 
00215         answer = None
00216         if 'dontapprove' in form:
00217             answer = self.handle_response(False, username, identity)
00218             if answer is None:
00219                 return
00220         elif form.has_key('approve'):
00221             answer = self.handle_response(True, username, identity)
00222             if answer is None:
00223                 return
00224         else:
00225             query = {}
00226             for key in form:
00227                 query[key] = form[key]
00228             try:
00229                 openidreq = openidsrv.decodeRequest(query)
00230             except Exception, e:
00231                 request.makeForbidden(403, 'OpenID decode error: %r' % e)
00232                 return
00233 
00234             if openidreq is None:
00235                 request.makeForbidden(403, 'no request')
00236                 return
00237 
00238             if request.user.valid and username != request.user.name:
00239                 answer = openidreq.answer(False, identity=identity, server_url=server_url)
00240             elif openidreq.mode in ["checkid_immediate", "checkid_setup"]:
00241                 answer = self.handleCheckIDRequest(identity, username, openidreq, server_url)
00242                 if answer is None:
00243                     return
00244             else:
00245                 answer = openidsrv.handleRequest(openidreq)
00246         webanswer = openidsrv.encodeResponse(answer)
00247         request.status = '%d OpenID status' % webanswer.code
00248         for hdr in webanswer.headers:
00249             request.headers.add(hdr, webanswer.headers[hdr])
00250         request.write(webanswer.body)
00251         raise MoinMoinFinish
00252 
00253     def handle_response(self, positive, username, identity):
00254         request = self.request
00255         form = request.form
00256 
00257         # check form submission nonce, use None for stored value default
00258         # since it cannot be sent from the user
00259         session_nonce = self.request.session.get('openidserver.nonce')
00260         if session_nonce is not None:
00261             del self.request.session['openidserver.nonce']
00262         # use empty string if nothing was sent
00263         form_nonce = form.get('nonce', '')
00264         if session_nonce != form_nonce:
00265             self.request.makeForbidden(403, 'invalid nonce')
00266             return None
00267 
00268         openidreq = request.session.get('openidserver.request')
00269         if not openidreq:
00270             request.makeForbidden(403, 'no response request')
00271             return None
00272         del request.session['openidserver.request']
00273 
00274         if (not positive or
00275             not request.user.valid or
00276             request.user.name != username):
00277             return openidreq.answer(False)
00278 
00279 
00280         if form.get('remember', 'no') == 'yes':
00281             if not hasattr(request.user, 'openid_trusted_roots'):
00282                 request.user.openid_trusted_roots = []
00283             request.user.openid_trusted_roots.append(strbase64(openidreq.trust_root))
00284             request.user.save()
00285         dummyidentity, server_url = self._make_identity()
00286         return self.approved(identity, openidreq, server_url=server_url)
00287 
00288     def approved(self, identity, openidreq, server_url=None):
00289         # TODO: If simple registration is implemented, this needs
00290         #       to do something like the following:
00291         #
00292         #       sreg_data = { fill this dict with real values }
00293         #       sreq_req = sreg.SRegRequest.fromOpenIDRequest(openidreq.message)
00294         #       # do something with the request to see what values are required?
00295         #       sreg_resp = sreg.SRegResponse.extractResponse(openidreq, sreg_data)
00296         #       sreg_resp.addToOpenIDResponse(reply.fields)
00297 
00298         reply = openidreq.answer(True, identity=identity, server_url=server_url)
00299         return reply
00300 
00301     def user_trusts_url(self, trustroot):
00302         user = self.request.user
00303         if hasattr(user, 'openid_trusted_roots'):
00304             return strbase64(trustroot) in user.openid_trusted_roots
00305         return False
00306 
00307     def show_decide_page(self, identity, username, openidreq):
00308         request = self.request
00309         _ = self._
00310 
00311         if not request.user.valid or username != request.user.name:
00312             request.makeForbidden(403, _('''You need to manually go to your OpenID provider wiki
00313 and log in before you can use your OpenID. MoinMoin will
00314 never allow you to enter your password here.
00315 
00316 Once you have logged in, simply reload this page.'''))
00317             return
00318 
00319         request.theme.send_title(_("OpenID Trust verification"), pagename=request.page.page_name)
00320         # Start content (important for RTL support)
00321         request.write(request.formatter.startContent("content"))
00322 
00323         request.write(request.formatter.paragraph(1))
00324         request.write(_('The site %s has asked for your identity.') % openidreq.trust_root)
00325         request.write(request.formatter.paragraph(0))
00326         request.write(request.formatter.paragraph(1))
00327         request.write(_('''
00328 If you approve, the site represented by the trust root below will be
00329 told that you control the identity URL %s. (If you are using a delegated
00330 identity, the site will take care of reversing the
00331 delegation on its own.)''') % openidreq.identity)
00332         request.write(request.formatter.paragraph(0))
00333 
00334         form = html.FORM(method='POST', action=request.page.url(request))
00335         form.append(html.INPUT(type='hidden', name='action', value='serveopenid'))
00336         form.append(html.INPUT(type='hidden', name='openid.identity', value=openidreq.identity))
00337         form.append(html.INPUT(type='hidden', name='openid.return_to', value=openidreq.return_to))
00338         form.append(html.INPUT(type='hidden', name='openid.trust_root', value=openidreq.trust_root))
00339         form.append(html.INPUT(type='hidden', name='openid.mode', value=openidreq.mode))
00340         form.append(html.INPUT(type='hidden', name='name', value=username))
00341 
00342         nonce = randomString(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
00343         form.append(html.INPUT(type='hidden', name='nonce', value=nonce))
00344         request.session['openidserver.nonce'] = nonce
00345 
00346         table = html.TABLE()
00347         form.append(table)
00348 
00349         tr = html.TR()
00350         table.append(tr)
00351         tr.append(html.TD().append(html.STRONG().append(html.Text(_('Trust root')))))
00352         tr.append(html.TD().append(html.Text(openidreq.trust_root)))
00353 
00354         tr = html.TR()
00355         table.append(tr)
00356         tr.append(html.TD().append(html.STRONG().append(html.Text(_('Identity URL')))))
00357         tr.append(html.TD().append(html.Text(identity)))
00358 
00359         tr = html.TR()
00360         table.append(tr)
00361         tr.append(html.TD().append(html.STRONG().append(html.Text(_('Name')))))
00362         tr.append(html.TD().append(html.Text(username)))
00363 
00364         tr = html.TR()
00365         table.append(tr)
00366         tr.append(html.TD().append(html.STRONG().append(html.Text(_('Remember decision')))))
00367         td = html.TD()
00368         tr.append(td)
00369         td.append(html.INPUT(type='checkbox', name='remember', value='yes'))
00370         td.append(html.Text(_('Remember this trust decision and don\'t ask again')))
00371 
00372         tr = html.TR()
00373         table.append(tr)
00374         tr.append(html.TD())
00375         td = html.TD()
00376         tr.append(td)
00377 
00378         td.append(html.INPUT(type='submit', name='approve', value=_("Approve")))
00379         td.append(html.INPUT(type='submit', name='dontapprove', value=_("Don't approve")))
00380 
00381         request.write(unicode(form))
00382 
00383         request.write(request.formatter.endContent())
00384         request.theme.send_footer(request.page.page_name)
00385         request.theme.send_closing_html()
00386 
00387     def _sorry_no_identity(self):
00388         request = self.request
00389         _ = self._
00390 
00391         request.theme.send_title(_("OpenID not served"), pagename=request.page.page_name)
00392         # Start content (important for RTL support)
00393         request.write(request.formatter.startContent("content"))
00394 
00395         request.write(request.formatter.paragraph(1))
00396         request.write(_('''
00397 Unfortunately you have not created your homepage yet. Therefore,
00398 we cannot serve an OpenID for you. Please create your homepage first
00399 and then reload this page or click the button below to cancel this
00400 verification.'''))
00401         request.write(request.formatter.paragraph(0))
00402 
00403         form = html.FORM(method='POST', action=request.page.url(request))
00404         form.append(html.INPUT(type='hidden', name='action', value='serveopenid'))
00405 
00406         nonce = randomString(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
00407         form.append(html.INPUT(type='hidden', name='nonce', value=nonce))
00408         request.session['openidserver.nonce'] = nonce
00409 
00410         form.append(html.INPUT(type='submit', name='dontapprove', value=_("Cancel")))
00411 
00412         request.write(unicode(form))
00413 
00414         request.write(request.formatter.endContent())
00415         request.theme.send_footer(request.page.page_name)
00416         request.theme.send_closing_html()