#! /usr/bin/env python
#############################################################
#                                                           #
#   Author: Olivier Sallou                                  #
#   Organization: GenOuest BioInformatics Platform,         #
#                IRISA, Rennes.                             #
#   Distributed under GPLv2 Licence. Please refer to the    #
#   COPYING.LIB document.                                   #
#                                                           #
#############################################################

from Cookie import SimpleCookie
import os
import cgi
import urlparse
import cgitb
import sys
import mb_cgi
from mb_cgi import MbCGI
from Mobyle.MobyleError import AuthenticationError,SessionError
import Mobyle.Net
import glob
import StringIO
from simpletal import simpleTAL, simpleTALES
from simpletal.simpleTALUtils import FastStringOutput


def quoteattr(s):
    qs = cgi.escape(s, 1)
    return '"%s"' % (qs,)


try:
    import openid
except ImportError:
    sys.stderr.write("""
Failed to import the OpenID library. In order to use this example, you
must either install the library (see INSTALL in the root of the
distribution) or else add the library to python's import path (the
PYTHONPATH environment variable).

For more information, see the README in the root of the library
distribution or http://www.openidenabled.com/
""")
    sys.exit(1)

from openid.store import memstore
from openid.store import filestore
from openid.consumer import consumer
from openid.oidutil import appendArgs
from openid.cryptutil import randomString
from openid.fetchers import setDefaultFetcher, Urllib2Fetcher
from openid.extensions import pape, sreg, ax



def setSessionCookie(self):
    sid = self.getSession()['id']
    session_cookie = '%s=%s;' % (self.SESSION_COOKIE_NAME, sid)
    self.send_header('Set-Cookie', session_cookie)

def process(self):
    """Dispatching logic. There are three paths defined:

      / - Display an empty form asking for an identity URL to
          verify
      /verify - Handle form submission, initiating OpenID verification
      /process - Handle a redirect from an OpenID server

    Any other path gets a 404 response. This function also parses
    the query parameters.

    If an exception occurs in this function, a traceback is
    written to the requesting browser.
    """    
    if(self.cfg.openid()==False):
        self.errMsg = '<div id="entete"><h2>Sorry, OpenId is not setup for this portal</h2></div>'
        return

    try:
        formulaire = cgi.FieldStorage()
        path = self.step
        path = formulaire.getvalue('step')
        if path == 'init':
            self.step = "init"
            self.showForm()
        elif path == 'verify':
            self.step = "verify"
            self.openidurl = formulaire.getvalue('openidurl')
            self.doVerify()
        elif path == 'process':
            self.step="process"
            self.openidurl = formulaire.getvalue('openidurl')
            self.doProcess()
        else:
            self.step= None
            #self.notFound()

    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        self.errMsg = sys.exc_info()





def notFound(self):
    """Render a page with a 404 return code and a message."""
    fmt = 'The path <q>%s</q> was not understood by this server.'
    msg = fmt % (self.path,)
    openid_url = self.query.get('openid_identifier')
    self.render(msg, 'error', openid_url, status=404)


def main():
    # Instantiate OpenID consumer store and OpenID consumer.  If you
    # were connecting to a database, you would create the database
    # connection and instantiate an appropriate store here   

    OPENIDCGI(processFunction=process,useSession=True)



class OPENIDCGI(MbCGI):

    step = "init"
    openidurl = None
    openidsession = {}
    errMsg = None
    trust_root = None
    return_to = None
    html = None
    htmlHeader = None
    store = None
    # Optionally set it in config to specify file manager usage
    data_path = None

    def requestRegistrationData(self, request):
        sreg_request = sreg.SRegRequest(
            required=['email'], optional=['fullname', 'nickname'])
        request.addExtension(sreg_request)
        sreg_ax_request = ax.FetchRequest()
        sreg_ax_request.add(
            ax.AttrInfo('http://axschema.org/contact/email',
                        required=True))
        request.addExtension(sreg_ax_request)

    def requestPAPEDetails(self, request):
        pape_request = pape.Request([pape.AUTH_PHISHING_RESISTANT])
        request.addExtension(pape_request)


    def getConsumer(self, stateless=False):

        if(self.cfg.openidstore_path() is not None):
            self.data_path = self.cfg.openidstore_path()

        if self.store is None:
            if self.data_path:
                self.store = filestore.FileOpenIDStore(self.data_path)
            else:
                self.store = memstore.MemoryStore()
        if stateless:
            store = None
        else:
            store = self.store
        return consumer.Consumer(self.openidsession, self.store)


    def showForm(self):
        os.chdir(self.osDir)
        self.cssDir = '/'+self.cfg.portal_url(True)+"/css/"
        self.jsDir = '/'+self.cfg.portal_url(True)+"/js/"
        self.htDir = '/'+self.cfg.portal_url(True)+"/"
        self.homeDir = self.cfg.cgi_url()

        self.context = simpleTALES.Context(allowPythonPath=1)
        templateFile = open('openidform.html')
        self.template = simpleTAL.compileHTMLTemplate (templateFile)
        templateFile.close()
        self.context.addGlobal("self", self)
        html = StringIO.StringIO()
        self.template.expand (self.context, html)
        self.html =  html.getvalue()
        #self.html = '<form action="openidconsumer.py" method="get">\n'
        #self.html += '<label>OpenID URL</label>\n'
        #self.html += '<input name="openidurl" type="text"/>\n'
        #self.html += '<input name="step" type="hidden" value="verify"/>\n'
        #self.html += '<input type="submit"/>\n'
        #self.html += "</form>\n"


    def render(self):
        #print "Content-type: text/html\n"
        if(self.htmlHeader is not None):
            print self.htmlHeader
        print "Content-type: text/html\n"
        print "\n"
        if(self.errMsg is not None):
            print "<html><head></head><body>\n"
            print self.errMsg
            print "</body></html>\n"
            return
        if (self.html is not None):
            print "<html><head></head><body>\n"
            if self.step == 'init':
                print self.html
            elif self.step == 'verify':
                print self.html
            elif self.step == 'process':
                print self.html
            else:
                print "Error, could not found any rendering for this operation\n"
            print "</body></html>\n"
        #else:
        #    redirect_url = self.request.redirectURL(
        #        self.trust_root, self.return_to, immediate=immediate)

                
    def doVerify(self):
        """Process the form submission, initating OpenID verification.
        """
        sid = randomString(16, '0123456789abcdef')
        self.openidsession = {}
        self.openidsession['id'] = sid
        # First, make sure that the user entered something
        openid_url = self.openidurl     
        if not openid_url:
            self.errMsg = 'Enter an OpenID Identifier to verify.'
            return
        self.openidsession['openidurl'] = self.openidurl

        immediate = False
        use_sreg = True
        use_pape = False
        use_stateless = False
        oidconsumer = self.getConsumer(stateless = use_stateless)

        if oidconsumer is None:
            mb_cgi.c_log.error('ERROR: oidconsumer is None')
            return

        try:
            request = oidconsumer.begin(openid_url)           
        except consumer.DiscoveryFailure, exc:
            mb_cgi.c_log.error('DISCOVERYFAILURE')
            self.errMsg = 'Error in discovery: %s' % (
                cgi.escape(str(exc[0])))
            mb_cgi.c_log.error(self.errMsg)
        else:
            if request is None:
                self.errMsg = 'No OpenID services found for <code>%s</code>' % (
                    cgi.escape(openid_url),)
            else:
                # Then, ask the library to begin the authorization.
                # Here we find out the identity server that will verify the
                # user's identity, and get a token that allows us to
                # communicate securely with the identity server.
                if use_sreg:
                    self.requestRegistrationData(request)
 
                if use_pape:
                    self.requestPAPEDetails(request)

                self.trust_root = self.cfg.root_url()
                self.return_to = self.cfg.cgi_url()+"/openidconsumer.py?step=process&password="+self.openidurl+"&openidurl="+self.openidurl


                # store auth data with anonymous session
                self.session.addOpenIdAuthData(self.openidsession)

                if not request.shouldSendRedirect():
                    self.html = request.htmlMarkup(
                        self.trust_root, self.return_to,
                        form_tag_attrs={'id':'openid_message'},
                        immediate=immediate)
                else:
                    redirect_url = request.redirectURL(
                            trust_root, return_to, immediate=immediate)
                    self.htmlHeader = 'Location: '+redirect_url+"\n"
                    self.html = ""



    def doProcess(self):
        """Handle the redirect from the OpenID server.
        """
        self.openidsession = self.session.getOpenIdAuthData()

        oidconsumer = self.getConsumer()

        # Ask the library to check the response that the server sent
        # us.  Status is a code indicating the response type. info is
        # either None or a string containing more information about
        # the return type.
        url = self.cfg.cgi_url()+"/openidconsumer.py?step=process"

	query = {}

        form = cgi.FieldStorage()
        for name in form.keys():
            value = form[name].value
            query[name] = value


        info = oidconsumer.complete(query, url)

        sreg_resp = None
        ax_items = None
        mail = None
        pape_resp = None
        css_class = 'error'
        display_identifier = info.getDisplayIdentifier()

        if info.status == consumer.FAILURE and display_identifier:
            # In the case of failure, if info is non-None, it is the
            # URL that we were verifying. We include it in the error
            # message to help the user figure out what happened.
            self.html = "Fail to authentify: "+display_identifier+","+info.message+"\n"
        elif info.status == consumer.SUCCESS:
            # Success means that the transaction completed without
            # error. If info is None, it means that the user cancelled
            # the verification.
            css_class = 'alert'

            # This is a successful verification attempt. If this
            # was a real application, we would do our login,
            # comment posting, etc. here.
            self.html = "You have successfully verified your identity."
            sreg_resp = sreg.SRegResponse.fromSuccessResponse(info)
            pape_resp = pape.Response.fromSuccessResponse(info)
            if info.endpoint.canonicalID:
                # You should authorize i-name users by their canonicalID,
                # rather than their more human-friendly identifiers.  That
                # way their account with you is not compromised if their
                # i-name registration expires and is bought by someone else.
                self.html += "  This is an i-name, and its persistent ID is "+info.endpoint.canonicalID+"\n"

            ax_response = ax.FetchResponse.fromSuccessResponse(info)
            if ax_response:
                ax_items = {
                    'email': ax_response.get(
                        'http://axschema.org/contact/email'),
                    }
            if not sreg_resp is None:
                mail = sreg_resp.get('email')
                #sreg_list = sreg_resp.items()
                #sreg_list.sort()
                #for k, v in sreg_list:
                #    field_name = sreg.data_fields.get(k, k)
                #    value = cgi.escape(v.encode('UTF-8'))
                #    self.html += field_name+"="+value+"\n"
            if mail is None:
                mail = ax_items['email'][0]

            if mail is None:
                self.html += "No registration data was returned\n"
                return
            else:
                #get email
                self.email = Mobyle.Net.EmailAddress( mail )                

            # Now create AuthenticatedSession for user
            try:
                self.anonymousSession = self.session
                self.session = self.sessionFactory.getOpenIdAuthenticatedSession(self.email)
            except SessionError,AuthenticationError:
                # Session does not exist create it
                self.session = self.sessionFactory.createAuthenticatedSession(Mobyle.Net.EmailAddress( mail ),passwd="")
            if self.anonymousSession:
                try:
                    self.session.mergeWith(self.anonymousSession)
                    self.sessionKey = self.session.getKey()
                    self.read_session()
                except SessionError, e:
                    self.errMsg = str(e)



            self.htmlHeader = "Location: "+self.cfg.cgi_url()+"/portal.py\n"
            self.html = ""


        elif info.status == consumer.CANCEL:
            # cancelled
            self.html = 'Verification cancelled'
        elif info.status == consumer.SETUP_NEEDED:
            if info.setup_url:
                self.html = "<a href="+quoteattr(info.setup_url)+">Setup needed</a>"
            else:
                # This means auth didn't succeed, but you're welcome to try
                # non-immediate mode.
                self.html = 'Setup needed'
        else:
            # Either we don't understand the code or there is no
            # openid_url included with the error. Give a generic
            # failure message. The library should supply debug
            # information in a log.
            self.html = 'Verification failed.'



if __name__ == '__main__':
    main()
