import libxml2
import urllib
import urlparse
import httplib
import re
import os
import socket
import base64

from quixote import get_request, get_response, get_session, redirect, get_publisher
from quixote.directory import Directory
from quixote.http_request import parse_header

import lasso

from qommon.form import *
from qommon.errors import ConnectionError, ConfigurationError, LoginError
from qommon.misc import http_post_request, http_get_page
from qommon.template import *

import logger
import misc
import storage
from users import User
from hosts import Host
from federations import Federation

class LocalAuthentication:
    def federate(self, username, password, provider_id, cookies, select):
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
        user = get_session().get_user(provider_id)
        if user is not None:
            Federation(username, password, host.id, user.name_identifiers[0], cookies, select).store()

#     def federate_token(self, token):
#         token_dir = os.path.join(misc.get_proxied_site_path(), 'tokens')
#         token_file_name = os.path.join(token_dir, token)
#         token_file = open(token_file_name, 'r')
#         username, user_passwd = token_file.read().split()        
#         token_file.close()

    def sso_local_login(self, federation):
        status, data = self.local_auth_check_dispatch(
            federation.username, federation.password, federation.select_fields)
        success, return_content = self.check_auth(status, data)
        if success:
            session = get_session()
            if hasattr(session, 'cookies'):
                federation.set_cookies(session.cookies)
                federation.store()
            return return_content
        else:
            return redirect('local_auth')

    def local_auth [html] (self, first_time=True):
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])

        response = get_response()
        response.set_content_type('text/html')
        self.local_auth_default(host, first_time)
        # We can use it to make a more specific/integrated authentication page
        #function_name = 'local_auth_' + host.name
        #getattr(self, str(function_name))(host)

# Example of such a specific method
#    def local_auth_xxx [html] (self, host):
#        get_response().filter.update({'no_template': True})
#        page = misc.get_html_page(host.auth_form)
#        old_url = re.compile(str('(<form action=")[^"]*"'))
#        page = old_url.sub(str(r"""<p>Please type your login and password for Dotclear.
#            Your account will be federated with your Liberty Alliance account</p>
#            \1local_auth_check"
#            """), page)
#        remember_checkbox = re.compile(str('<p><input type="checkbox" id="user_remember".*?</p>'), re.DOTALL)
#        page = remember_checkbox.sub(str(r''), page)
#        htmltext(page)


    def local_auth_default [html] (self, host, first_time=True):
        if hasattr(get_response(), str('breadcrumb')):
            del get_response().breadcrumb

        get_response().filter['default_org'] = '%s - %s' % (host.label, _('Local authentication')) 
        get_response().filter['body_class'] = 'login'
        
        form = form = Form(enctype='multipart/form-data')
        form.add(StringWidget, 'username', title = _('Username'), required = True,
                size = 30)
        form.add(PasswordWidget, 'password', title = _('Password'), required = True,
                size = 30)
        for name, values in host.select_fields.iteritems():
            options = []
            for value in values:
                options.append((value, value.capitalize()))
            form.add(SingleSelectWidget, name, title = name.capitalize(),
                value = values[0], options = options)
        form.add_submit('submit', _('Submit'))
        #form.add_submit('cancel', _('Cancel'))
        
#        if form.get_widget('cancel').parse():
#            return redirect('.')
        authentication_failure = None
        if form.is_submitted() and not form.has_errors():
            username = form.get_widget('username').parse()
            password = form.get_widget('password').parse()
            select = {}
            for name, values in host.select_fields.iteritems():
                select[name] = form.get_widget(name).parse()
            try:
                return self.local_auth_check(username, password, select)
            except LoginError:
                authentication_failure = _('Authentication failure')
                logger.info('local auth page : %s' % authentication_failure)
            except ConnectionError, err:
                authentication_failure = _('Connection failed : %s') % err
                logger.info('local auth page : %s' % authentication_failure)
            except ConfigurationError, err:
                authentication_failure = _('This service provider is not fully configured : %s') % err
                logger.info('local auth page : %s' % authentication_failure)
            except Exception, err:
                authentication_failure = _('Unknown error : %s' % err)
                logger.info('local auth page : %s' % authentication_failure)

        if authentication_failure:
            '<div class="errornotice">%s</div>' % authentication_failure
        '<p>'
        _('Please type your login and password for this Service Provider.')
        _('Your local account will be federated with your Liberty Alliance account.')
        '</p>'
        
        form.render()

    def local_auth_check(self, username, password, select={}):
        host = Host.get_host_from_url()
        assert host is not None
        status, data = self.local_auth_check_dispatch(username, password, select)
        if status == 0:
            raise            
        success, return_content = self.check_auth(status, data)
        if success:
            session = get_session()
            if hasattr(session, 'cookies'):
                self.federate(username, password, host.provider_id, session.cookies, select)
            else:
                self.federate(username, password, host.provider_id, None, select)
            return return_content
        raise LoginError()

    def local_auth_check_dispatch(self, username, password, select={}):
        host = Host.get_host_from_url()
        assert host is not None
        if host.auth_mode == 'http_basic':
            return self.local_auth_check_http_basic(host, username, password)
        elif host.auth_mode == 'form' and hasattr(host, 'auth_check_url') and host.auth_check_url is not None:
            return self.local_auth_check_post(host, username, password, select)
        else:
            raise ConfigurationError('No authentication form was found')

    def local_auth_check_post(self, host, username, password, select={}):
        url = host.auth_check_url

        # Build request body
        body = '%s=%s&%s=%s' % (host.login_field_name, username, host.password_field_name, password)
        # Add select fields to the body
        for name, value in select.iteritems():
            body += '&%s=%s' % (name, value)
        # Add hidden fields to the body
        if host.send_hidden_fields:
            for key, value in host.other_fields.iteritems():
                body += '&%s=%s' % (key, value)

        # Build request HTTP headers
        # FIXME : Add back {'Host': hostname}
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
                   'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
                   'X-Forwarded-Host': host.reversed_hostname}
        
        # Send request
        response, status, data, auth_headers = http_post_request(url, body, headers)

        # For egroupware, follow the http redirects to get the right page
        if host.name.startswith('egroupware'):
            data = self.get_data_after_redirects(host, response, data)

        cookies = response.getheader('Set-Cookie', None)
        host.cookies = []
        if cookies is not None:
            cookies_list = []
            cookies_set_list = []
            for cookie in cookies.split(', '):
                # Drop the path and other attributes
                cookie_only = cookie.split('; ')[0]
                regexp = re.compile('=')
                if regexp.search(cookie_only) is None:
                    continue
                # Split name and value
                cookie_split = cookie_only.split('=')
                cookie_name = cookie_split[0]
                cookie_value = cookie_split[1]
                cookies_list.append('%s=%s' % (cookie_name, cookie_value))
                set_cookie = '%s=%s; path=/' % (cookie_name, cookie_value)
                cookies_set_list.append(set_cookie)
                host.cookies.append(cookie_name)
            cookies_headers = '\r\nSet-Cookie: '.join(cookies_set_list)
            get_response().set_header('Set-Cookie', cookies_headers)
            host.store()
            get_session().cookies = '; '.join(cookies_list)
        else:
            logger.warn('No cookie from local authentication')
        return response.status, data

    def get_data_after_redirects(self, host, response, data):
        status = response.status
        last_response = response
        headers = {'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
                   'X-Forwarded-Host': host.reversed_hostname}
        while status == 302:
            location = last_response.getheader('Location', None)
            if location is not None:
                # FIXME : replace this code with http_get_page or FancyURLOpener
                url = host.auth_check_url
                if url.startswith('http://'):
                    hostname, query = urllib.splithost(url[5:])
                    conn = httplib.HTTPConnection(hostname)
                else:
                    hostname, query = urllib.splithost(url[6:])
                    conn = httplib.HTTPSConnection(hostname)
                try:
                    conn.request('GET', location, headers=headers)
                except socket.gaierror, err:
                    print err
                    status = 200
                else:
                    last_response = conn.getresponse()
                    status = last_response.status
                    data = last_response.read()
                conn.close()
            else:
                status = 200
        return data

    def local_auth_check_http_basic(self, host, username, password):
        url = host.auth_form_url
        hostname, query = urllib.splithost(url[5:])
        conn = httplib.HTTPConnection(hostname)

        auth_header = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))

        try:
            conn.request('GET', query, headers={'Authorization': auth_header})
        except socket.gaierror, err:
            print err
            conn.close()
            return 0, None
        else:
            response = conn.getresponse()
            conn.close()
            return response.status, response.read()

    def check_auth(self, status, data):
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
        success = False
        return_content = ''
        
        # For http auth, only check status code
        if host.auth_mode == 'http_basic':
            # If failed, status code should be 401
            if status // 100 == 2 or status // 100 == 3:
                success = True
                return_content = redirect(host.get_return_url())

        else:
            if host.auth_system == 'password':
                # If there is a password field, authentication probably failed
                regexp = re.compile("""<input[^>]*?type=["']?password["']?[^>]*?>""", re.DOTALL | re.IGNORECASE)
                if not regexp.findall(data):
                    success = True
                    # FIXME : avoid using these urls in the code if possible
                    if host.orig_site.startswith('http://listes.entrouvert.com') \
                            or host.orig_site.startswith('https://listes.libre-entreprise.org'):
                        get_response().filter.update({'no_template': True})
                        return_content = htmltext(data)
                    else:
                        return_content = redirect(host.get_return_url())
            elif host.auth_system == 'status':
                match_status = int(host.auth_match_status)
                if match_status == status:
                    success = True
                    return_content = redirect(host.return_url)
            elif host.auth_system == 'match_text':
                # If the auth_match_text is not matched, it means the authentication is successful
                regexp = re.compile(host.auth_match_text, re.DOTALL)
                if not regexp.findall(data):
                    success = True
                    return_content = redirect(host.get_return_url())

#        if status == 302:
#            success = True
#            return_content = redirect(host.return_url)
#        else:
#            if host.orig_site.startswith('http://listes.entrouvert.com') \
#                    and re.search('You have logged in', data) is not None:
#                success = True
#                return_content = data
        return success, return_content


    def local_logout(self, provider_id=None, federation=None, user=None):
        if provider_id is not None:
            try:
                hosts = list(Host.select(lambda x: x.provider_id == provider_id))
                host = hosts[0]
            except KeyError:
                return
        else:
            host = Host.get_host_from_url()
            if host is None:
                return

        if federation is None and user is not None:
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
            if federations:
                federation = federations[0]

        # Logout request to the site
        url = host.logout_url            
        if url is not None and federation is not None and federation.cookies is not None:
            try:
                http_get_page(url, {'Cookie': federation.cookies})
            except:
                pass

        # Remove cookies from the browser
        if hasattr(host, 'cookies'):
            for cookie in host.cookies:
                get_response().expire_cookie(cookie, path='/')
#        function_name = 'local_logout_' + host.name
#        getattr(self, str(function_name))()

    def local_defederate(self, session, provider_id):
        if session is None:
            return
        user = session.get_user(provider_id)
        if user is not None:
            federations = Federation.select(lambda x: user.name_identifiers[0] in x.name_identifiers)
            for federation in federations:
                self.local_logout(provider_id, federation)
                federation.remove_name_identifier(user.name_identifiers[0])
                federation.store()

