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

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

import lasso

import logger
import misc
import storage
from form import *
from template import *
from users import User
from hosts import Host
from federations import Federation

class LoginError(Exception):
    pass

class Liberty(Directory):
    _q_exports = ['', 'login', 'assertionConsumer', 'soapEndpoint',
            'singleLogout', 'singleLogoutReturn',
            'federationTermination', 'federationTerminationReturn',
            ('metadata.xml', 'metadata'), 'public_key',
            'local_auth']

    def perform_login(self, idp = None):
        server = misc.get_lasso_server()
        login = lasso.Login(server)
        login.initAuthnRequest(idp, lasso.HTTP_METHOD_REDIRECT)
        login.request.nameIdPolicy = 'federated'
        login.request.forceAuthn = False
        login.request.isPassive = False
        login.request.consent = 'urn:liberty:consent:obtained'
        login.buildAuthnRequestMsg()
        return redirect(login.msgUrl)

    def assertionConsumer(self):
        server = misc.get_lasso_server()
        if not server:
            return error_page(_('Liberty support is not yet configured'))
        login = lasso.Login(server)
        request = get_request()
        if request.get_method() == 'GET' or get_field('LAREQ'):
            if request.get_method() == 'GET':
                login.initRequest(request.get_query(), lasso.HTTP_METHOD_REDIRECT)
            else:
                login.initRequest(get_field('LAREQ'), lasso.HTTP_METHOD_POST)

            login.buildRequestMsg()
            try:
                soap_answer = soap_call(login.msgUrl, login.msgBody)
            except SOAPException:
                return error_page(_('Failure to communicate with identity provider'))
            try:
                login.processResponseMsg(soap_answer)
            except lasso.Error, error:
                if error[0] == lasso.LOGIN_ERROR_STATUS_NOT_SUCCESS:
                    return error_page(_('Unknown authentication failure'))
                if hasattr(lasso, 'LOGIN_ERROR_UNKNOWN_PRINCIPAL'):
                    if error[0] == lasso.LOGIN_ERROR_UNKNOWN_PRINCIPAL:
                        return error_page(_('Authentication failure; unknown principal'))
                return error_page(_("Identity Provider didn't accept artifact transaction."))
        else:
            login.processAuthnResponseMsg(get_field('LARES'))
        login.acceptSso()
        session = get_session()
        if login.isSessionDirty:
            if login.session:
                session.lasso_session_dumps[server.providerId] = login.session.dump()
            else:
                session.lasso_session_dumps[server.providerId] = None

        # Look for an existing user
        user = self.lookup_user(session, login)

        # Check if it is for Larpe administration or token
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
        if host.name == 'larpe':
            session.name_identifier = login.nameIdentifier.content
            session.lasso_dump = login.identity.dump()
            session.provider_id = server.providerId

            if user:
                session.set_user(user.id, server.providerId)
                if hasattr(session, 'after_url') and session.after_url.find('admin') != -1:
                    return redirect(session.after_url)
            else:
                if not user or not user.is_admin:
                    if hasattr(session, 'after_url') and session.after_url.find('token') != -1:
                        return redirect(session.after_url)
                    return redirect('%s/token' % get_request().environ['SCRIPT_NAME'])
            return redirect('%s/admin/' % get_request().environ['SCRIPT_NAME'])


        # Set session user
        if not user:
            user = User()
        user.name_identifiers = [ login.nameIdentifier.content ]
        user.lasso_dumps = [ login.identity.dump() ]
        user.store()    
        session.set_user(user.id, server.providerId)
                       
#        elif request.user is not None:
#            user = request.user
#            session.set_user(user.id)
#        else:
#            session.set_user('anonymous-%s' % login.nameIdentifier.content)
#            user = session.get_user(login.nameIdentifier.content)
        
#         if not user.name_identifiers.has_key(server.providerId):
#             user.name_identifiers[server.providerId] = [ login.nameIdentifier.content ]
#         elif not login.nameIdentifier.content in user.name_identifiers[server.providerId]:
#             user.name_identifiers[server.providerId].append(login.nameIdentifier.content)
#         user.lasso_dumps[server.providerId] = login.identity.dump()
#         user.store()
#         request.user = user

        federations = Federation.select(lambda x: host.id == x.host_id \
                                        and user.name_identifiers[0] in x.name_identifiers)

        if federations:
            return self.sso_local_login(federations[0])
        else:
            if hasattr(session, 'token'):
                self.federate_token(session.token)
            else:
                response = get_response()
                if session.after_url:
                    after_url = session.after_url
                    session.after_url = None
                    return redirect(after_url)
                response.set_status(303)
                response.headers['location'] = urlparse.urljoin(request.get_url(), str('local_auth'))
                response.content_type = 'text/plain'
                return 'Your browser should redirect you'

    def lookup_user(self, session, login): 
        found_users = list(User.select(lambda x: login.nameIdentifier.content in x.name_identifiers))
        if found_users:
            return found_users[0]
        return None

    def singleLogout(self):
#        host = Host.get_host_from_url()
#        if host is not None and host.name != 'larpe':
        request = get_request()
        logout = lasso.Logout(misc.get_lasso_server())
        if lasso.isLibertyQuery(request.get_query()):
            try:
                logout.processRequestMsg(request.get_query())
            except lasso.Error, error:
                if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
                    return error_page(_('Failed to check single logout request signature.'))
                raise
            session = get_session()
            if not session.id:
  	            # session has not been found, this may be because the user has
  	            # its browser configured so that cookies are not sent for
  	            # remote queries and IdP is using image-based SLO.
  	            # so we look up a session with the appropriate name identifier
                for session in get_session_manager().values():
                    user = session.get_user(logout.server.providerId)
                    if user and logout.nameIdentifier.content in user.name_identifiers:
                        break
                else:
                    session = get_session()
            return self.slo_idp(logout, session)
        else:
            return self.slo_sp(logout, get_session())

    def singleLogoutReturn(self):
        logout = lasso.Logout(misc.get_lasso_server())
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])

        try:
            logout.processResponseMsg(get_request().get_query())
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_INVALID_QUERY:
                raise AccessError()
            if error[0] == lasso.DS_ERROR_INVALID_SIGNATURE:
                return error_page(_('Failed to check single logout request signature.'))
            if hasattr(lasso, 'LOGOUT_ERROR_REQUEST_DENIED') and \
                    error[0] == lasso.LOGOUT_ERROR_REQUEST_DENIED:
                return redirect(host.get_root_url()) # ignore silently
            elif error[0] == lasso.ERROR_UNDEFINED:
                # XXX: unknown status; ignoring for now.
                return redirect(host.get_root_url()) # ignore silently
            raise
        return redirect(host.get_root_url())

    def slo_idp(self, logout, session):
        # Single Logout initiated by IdP
        if session.lasso_session_dumps.has_key(logout.server.providerId):
            logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
        user = session.get_user(logout.server.providerId)
        self.local_logout(logout.server.providerId, user=user)
#         if not user:
#             host = Host.get_host_from_url()
#             if host is None:
#                 return redirect('/')
#             print host.name
#             return redirect(host.get_root_url())
        if user and user.lasso_dumps:
            logout.setIdentityFromDump(user.lasso_dumps[0])
        if user and logout.nameIdentifier.content not in user.name_identifiers:
            raise 'No appropriate name identifier in user (%s and %s)' % (
                    logout.nameIdentifier.content, user.name_identifiers)

        try:
            logout.validateRequest()
        except lasso.Error, error:
            if error[0] != lasso.PROFILE_ERROR_SESSION_NOT_FOUND:
                raise
        else:
            get_session_manager().expire_session(logout.server.providerId) 

        logout.buildResponseMsg()
        if logout.msgBody: # soap answer
            return logout.msgBody
        else:
            return redirect(logout.msgUrl)

    def slo_sp(self, logout, session):
        if not session.id or not session.users.has_key(logout.server.providerId) \
                or not session.lasso_session_dumps.has_key(logout.server.providerId):
            get_session_manager().expire_session(logout.server.providerId)
            host = Host.get_host_from_url()
            if host is None:
                return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
            return redirect(host.get_root_url())
#         print session.users
#         print session.lasso_session_dumps
        logout.setSessionFromDump(session.lasso_session_dumps[logout.server.providerId])
        user = session.get_user(logout.server.providerId)
        self.local_logout(logout.server.providerId, user=user)
        if user and user.lasso_dumps:
            logout.setIdentityFromDump(user.lasso_dumps[0])
        return self.slo_sp_redirect(logout)

    def slo_sp_redirect(self, logout):
        try:
            logout.initRequest(None, lasso.HTTP_METHOD_REDIRECT)
        except lasso.Error, error:
            if error[0] == lasso.PROFILE_ERROR_NAME_IDENTIFIER_NOT_FOUND:
                get_session_manager().expire_session() 
                host = Host.get_host_from_url()
                if host is None:
                    return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
                return redirect(host.get_root_url())
            raise
        logout.buildRequestMsg()
        get_session_manager().expire_session(logout.server.providerId)
        return redirect(logout.msgUrl)

    def soapEndpoint(self):
        request = get_request()
        ctype = request.environ.get('CONTENT_TYPE')
        if not ctype:
            return

        ctype, ctype_params = parse_header(ctype)
        if ctype != 'text/xml':
            return

        response = get_response()
        response.set_content_type('text/xml')

        length = int(request.environ.get('CONTENT_LENGTH'))
        soap_message = request.stdin.read(length)

        request_type = lasso.getRequestTypeFromSoapMsg(soap_message) 

        if request_type == lasso.REQUEST_TYPE_LOGOUT:
            logout = lasso.Logout(misc.get_lasso_server())
            logout.processRequestMsg(soap_message)
            name_identifier = logout.nameIdentifier.content
            for session in get_session_manager().values():
                user = session.get_user(logout.server.providerId)
                if user and logout.nameIdentifier.content in user.name_identifiers:
                    break
            else:
                session = None
            return self.slo_idp(logout, session)

        if request_type == lasso.REQUEST_TYPE_DEFEDERATION:
            defederation = lasso.Defederation(misc.get_lasso_server())
            defederation.processNotificationMsg(soap_message)
            for session in get_session_manager().values():
                user = session.get_user(defederation.server.providerId)
#                print user
                if user and defederation.nameIdentifier.content in user.name_identifiers:
                    break
            else:
                session = None
            return self.fedterm(defederation, session)

    def federationTermination(self):
        request = get_request()
        if not lasso.isLibertyQuery(request.get_query()):
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
        
        defederation = lasso.Defederation(misc.get_lasso_server())
        defederation.processNotificationMsg(request.get_query())
        return self.fedterm(defederation, get_session())

    def fedterm(self, defederation, session):
#        host = Host.get_host_from_url()
#        if host is not None and host.name != 'larpe':

        if session is not None:
            self.local_defederate(session, defederation.server.providerId)
            if session.lasso_session_dumps.has_key(defederation.server.providerId):
                defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
            user = session.get_user(defederation.server.providerId)
            if user and user.lasso_dumps:
                defederation.setIdentityFromDump(user.lasso_dumps[0])
        else:
            user = None

        try:
            defederation.validateNotification()
        except lasso.Error, error:
            pass # ignore failure (?)
        else:
            if user:
                if not defederation.identity:
                    # if it was the last federation the whole identity dump collapsed
                    del user.lasso_dumps[0]
                else:
                    user.lasso_dumps[0] = defederation.identity.dump()
                user.store()

        if user and defederation.nameIdentifier.content:
            user.remove_name_identifier(defederation.server.providerId, defederation.nameIdentifier.content)
            user.store()

        if defederation.isSessionDirty and session is not None:
            if not defederation.session:
                del session.lasso_session_dumps[defederation.server.providerId]
            else:
                session.lasso_session_dumps[defederation.server.providerId] = defederation.session.dump()
            session.store()

        get_session_manager().expire_session(defederation.server.providerId)

        if defederation.msgUrl:
            return redirect(defederation.msgUrl)
        else:
            response = get_response()
            response.set_status(204)
            return ''

    def federationTerminationReturn(self):
        host = Host.get_host_from_url()
        if host is None:
            return redirect('%s/' % get_request().environ['SCRIPT_NAME'])
        return redirect(host.get_return_url())

#     def defederate(self, idp = None):
#         session = get_session()
#         user = get_request().user

#         server = misc.get_lasso_server()

#         self.local_defederate(session, server.providerId)
#         
#         defederation = lasso.Defederation(server)
#         defederation.setSessionFromDump(session.lasso_session_dumps[defederation.server.providerId])
#         if user and user.lasso_dumps.has_key(defederation.server.providerId):
#             defederation.setIdentityFromDump(user.lasso_dumps[defederation.server.providerId])

#         defederation.initNotification(idp, lasso.HTTP_METHOD_SOAP);
#         defederation.buildNotificationMsg();

#         try:
#             soap_call(defederation.msgUrl, defederation.msgBody);
#         except SOAPException:
#             return error_page(_('Failure to communicate with identity provider'))

#         rootUrl = '/' + misc.get_proxied_site_name() + '/'
#         return redirect(rootUrl)

    def federate(self, username, password, provider_id, cookies):
        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).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)
        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 self.local_auth(False)

    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(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, first_time=True):
        if hasattr(get_response(), str('breadcrumb')):
            del get_response().breadcrumb
        html_top(_('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)
        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()
            try:
                return self.local_auth_check(username, password)
            except LoginError:
                authentication_failure = _('Authentication Failure')
                logger.info('local auth page (after failed attempt)')

        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):
        host = Host.get_host_from_url()
        assert host is not None
        status, data = self.local_auth_check_dispatch(username, password)            
        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)
            else:
                self.federate(username, password, host.provider_id, None)
            return return_content
        raise LoginError()

    def local_auth_check_dispatch(self, username, password):
        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'):
            return self.local_auth_check_post(host, username, password)
        else:
            logger.error('This service provider is not properly configured : No authentication form was found')
            return 0, ''

    def local_auth_check_post(self, host, username, password):
        response, data = self.local_auth_check_post_request(host, username, password)

        # 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 = []
        misc.reload_cfg()
        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 local_auth_check_post_request(self, host, username, password):
        url = host.auth_check_url
        if url is None:
            return 0, ''
        if url.startswith('http://'):
            host_addr, query = urllib.splithost(url[5:])
            conn = httplib.HTTPConnection(host_addr)
        else:
            host_addr, query = urllib.splithost(url[6:])
            conn = httplib.HTTPSConnection(host_addr)
        query = query.replace('&amp;', '&')
        body = '%s=%s&%s=%s' % (host.login_field_name, username, host.password_field_name, password)
        if host.send_hidden_fields:
            for key, value in host.other_fields.iteritems():
                body += '&%s=%s' % (key, value)
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
                   'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
                   'X-Forwarded-Host': host.reversed_hostname}
        conn.request('POST', query, body, headers)
        response = conn.getresponse()
#        print response.getheader('Location', None)
        data = response.read()
        conn.close()
        return response, 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:
                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 == 'match_text':
                # If the auth_match_text is not matched, than 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
        if federation is not None and federation.cookies is not None:
            url = host.logout_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:
                headers = {'Cookie': federation.cookies,
                           'X-Forwarded-For': get_request().get_environ('REMOTE_ADDR', '-'),
                           'X-Forwarded-Host': host.reversed_hostname}
                conn.request('GET', query, headers=headers)
            except socket.gaierror, err:
                logger.warn(err)
                conn.close()

        # 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()

    def metadata(self):
        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/xml', 'utf-8')
        metadata = unicode(open(host.metadata).read(), 'utf-8')
        return metadata

    def public_key(self):
        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('application/octet-stream')
        public_key = open(host.public_key).read()
        return public_key


class SOAPException(Exception):
    pass


def soap_call(url, msg):
    if url.startswith('http://'):
        host, query = urllib.splithost(url[5:])
        conn = httplib.HTTPConnection(host)
    else:
        host, query = urllib.splithost(url[6:])
        conn = httplib.HTTPSConnection(host)
    conn.request('POST', query, msg, {'Content-Type': 'text/xml'})
    response = conn.getresponse()
    data = response.read()
    conn.close()
    if response.status not in (200, 204): # 204 ok for federation termination
        logger.warn('SOAP error (%s) (on %s)' % (response.status, url))
        raise SOAPException()
    return data
