#
# SchoolTool - common information systems platform for school administration
# Copyright (c) 2007 Shuttleworth Foundation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
"""
CAS authentication plugin for schooltool.
"""
import urllib

from zope.interface import implements
from zope.component import getUtility
from zope.component import provideUtility
from zope.session.interfaces import ISession
from zope.app.security.interfaces import IAuthentication
from zope.publisher.browser import BrowserView
from zope.traversing.browser.absoluteurl import absoluteURL

from schooltool.cas.interfaces import ICASAuthority
from schooltool.cas.interfaces import ICASAuthenticationPlugin
from schooltool.person.interfaces import IPerson
from schooltool.app.security import PersonContainerAuthenticationPlugin
from schooltool.app.interfaces import ISchoolToolApplication


def construct_url(environ):
    url = environ.get('wsgi.url_scheme', 'http') +  '://'
    url += environ.get('HTTP_HOST', '')
    url += urllib.quote(environ.get('PATH_INFO', ''))
    if environ.get('QUERY_STRING'):
        url += '?' + environ['QUERY_STRING']
    return url
        

class CASAuthenticationPlugin(PersonContainerAuthenticationPlugin):
    implements(ICASAuthenticationPlugin)

    @property
    def authority(self):
        return getUtility(ICASAuthority)

    def __init__(self):
        super(PersonContainerAuthenticationPlugin, self).__init__()

    def checkCredentials(self, request):
        session = ISession(request)[self.session_name]
        principal_id = session.get('principal_id', None)
        if principal_id:
            return True
        return session.get('authenticated', False)

    def getCredentials(self, request):
        session = ISession(request)[self.session_name]
        principal_id = session.get('principal_id', None)
        if principal_id:
            return self.getPrincipal(principal_id)

    def storeCredentials(self, request, principal):
        session = ISession(request)[self.session_name]
        if principal is not None:
            session['principal_id'] = principal.id
        session['authenticated'] = True

    def clearCredentials(self, request):
        session = ISession(request)[self.session_name]
        if 'principal_id' in session:
            del session['principal_id']
        del session['authenticated']

    def _urlopen(self, requrl):
        return urllib.urlopen(requrl).read()
        
    def authenticate(self, request):
        ticket = request.form.get('ticket')
        CAS = request.form.get('CAS')
        if ticket:
            # assume a response from the authority
            service = construct_url(request.environment)
            service = service[:service.find("ticket=") - 1]
            args = urllib.urlencode(
                {'service': service,'ticket': ticket})
            requrl = self.authority.server + "validate?" + args
            contactResult = self._urlopen(requrl)
            result = contactResult.split("\n")
            if 'yes' == result[0]:
                principal = self.getPrincipal('sb.person.' + result[1])
                self.restorePOSTData(request)
                self.storeCredentials(request, principal)
                return principal
        elif self.checkCredentials(request):
            return self.getCredentials(request)
        elif CAS:
            self.storeCredentials(request, None)
        else:
            form_id = self.storePOSTData(request)

            service = construct_url(request.environment)

            if "?" in service:
                service += "&CAS=1"
            else:
                service += "?CAS=1"

            if form_id:
                service += '&post_form=%s' % form_id
            args = urllib.urlencode(
                {'service': service, 'gateway': 'true'})
            requrl = self.authority.server + "login?" + args
            request.response.redirect(requrl)

    def unauthorized(self, id, request):
        """Signal an authorization failure."""
        service = construct_url(request.environment)
        if "ticket=" in service:
            service = service[:service.find("ticket=") - 1]

        form_id = self.storePOSTData(request)
        if form_id:
            if "?" in service:
                service += "&"
            else:
                service += "?"
            service += '&post_form=%s' % form_id

        args = urllib.urlencode(
            {'service': service, 'renew': 'true'})
        requrl = self.authority.server + "login?" + args
        request.response.redirect(requrl)

    def unauthenticatedPrincipal(self):
        """Return the unauthenticated principal, if one is defined."""
        return None


class LoginView(BrowserView):
    """A login view"""

    def __call__(self):
        auth = getUtility(IAuthentication)
        principal = auth.authenticate(self.request)
        person = IPerson(principal, None)
        if 'nexturl' in self.request:
            nexturl = self.request['nexturl']
        elif person is not None:
            nexturl = absoluteURL(person, self.request) + '/@@logindispatch'
        else:
            app = ISchoolToolApplication(None)
            nexturl = absoluteURL(app, self.request)
        args = urllib.urlencode(
            {'service': nexturl, 'renew': 'true'})
        requrl = auth.authPlugin.authority.server + "login?" + args
        self.request.response.redirect(requrl)


class LogoutView(BrowserView):
    """Clears the authentication creds from the session"""

    def __call__(self):
        auth = getUtility(IAuthentication)
        url = absoluteURL(ISchoolToolApplication(None), self.request)
        args = urllib.urlencode(
            {'url': url})
        requrl = auth.authPlugin.authority.server + "logout?" + args
        auth.clearCredentials(self.request)
        self.request.response.redirect(requrl)


class CASAuthorityUtility(object):
    implements(ICASAuthority)

    def __init__(self, server):
        self.server = server


def handle_configuration(options, context):
    cas_authority = getattr(options.config, 'cas_authority', None)
    if cas_authority:
        provideUtility(CASAuthorityUtility(cas_authority.server))
        context.provideFeature('cas_authentication')
        context.provideFeature('authentication')


def get_configuration():
    return ("""
<import package="schooltool.cas" />

<section type="cas_authority" name="*" required="no" attribute="cas_authority">
  <description>
    Configuration for CAS authentication plugin.
  </description>
  <example>
    &lt;cas_authority&gt;
      server https://localhost:4430/
    &lt;/cas_authority&gt;
  </example>
</section>

""", handle_configuration)
