~ralsina/ubuntu-sso-client/cross-platform

« back to all changes in this revision

Viewing changes to ubuntu_sso/utils/webclient/tests/test_webclient.py

  • Committer: Tarmac
  • Author(s): Manuel de la Pena
  • Date: 2012-03-09 14:33:28 UTC
  • mfrom: (846.10.21 qt-ssl-dialog)
  • Revision ID: tarmac-20120309143328-5uih024zxk5sh3js
- Added a translatable string to give more context of the ssl cert info to the user
  (LP: #948119).
- Provided the logic required for the Qt webclient implementation to detect ssl errors
  and spawn the ssl dialog to allow the user accept the ssl cert exceptions
  (LP: #948134).
- Changed the qt webclient implementation to use a proxy factory so that the correct
  proxy is chosen according to the request (LP: #950088).

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
"""Integration tests for the proxy-enabled webclient."""
17
17
 
18
18
import os
 
19
import shutil
19
20
import sys
20
21
import urllib
21
22
 
 
23
from OpenSSL import crypto
 
24
from socket import gethostname
22
25
from twisted.cred import checkers, portal
23
26
from twisted.internet import defer
24
27
from twisted.web import guard, http, resource
34
37
    USER_CANCELLATION,
35
38
)
36
39
from ubuntu_sso.utils import webclient
 
40
from ubuntu_sso.utils.ui import SSL_DETAILS_TEMPLATE
 
41
from ubuntu_sso.utils.webclient import gsettings
37
42
from ubuntu_sso.utils.webclient.common import BaseWebClient, HeaderDict, oauth
38
43
from ubuntu_sso.utils.webclient.tests import BaseMockWebServer
39
44
 
767
772
        got_creds = yield self.wc.request_proxy_auth_credentials(self.domain,
768
773
                                                           self.retry)
769
774
        self.assertFalse(got_creds, 'Return true when user cancels.')
 
775
 
 
776
 
 
777
class BaseSSLTestCase(SquidTestCase):
 
778
    """Base test that allows to use ssl connections."""
 
779
 
 
780
    @defer.inlineCallbacks
 
781
    def setUp(self):
 
782
        """Set the diff tests."""
 
783
        yield super(BaseSSLTestCase, self).setUp()
 
784
        self.cert_dir = os.path.join(self.tmpdir, 'cert')
 
785
        self.cert_details = dict(organization='Canonical',
 
786
                                 common_name=gethostname(),
 
787
                                 locality_name='London',
 
788
                                 unit='Ubuntu One',
 
789
                                 country_name='UK',
 
790
                                 state_name='London',)
 
791
        self.ssl_settings = self._generate_self_signed_certificate(
 
792
                                                             self.cert_dir,
 
793
                                                             self.cert_details)
 
794
        self.addCleanup(self._clean_ssl_certificate_files)
 
795
 
 
796
        self.ws = MockWebServer(self.ssl_settings)
 
797
        self.addCleanup(self.ws.stop)
 
798
        self.base_iri = self.ws.get_iri()
 
799
        self.base_ssl_iri = self.ws.get_ssl_iri()
 
800
 
 
801
    def _clean_ssl_certificate_files(self):
 
802
        """Remove the certificate files."""
 
803
        if os.path.exists(self.cert_dir):
 
804
            shutil.rmtree(self.cert_dir)
 
805
 
 
806
    def _generate_self_signed_certificate(self, cert_dir, cert_details):
 
807
        """Generate the required SSL certificates."""
 
808
        if not os.path.exists(cert_dir):
 
809
            os.makedirs(cert_dir)
 
810
        cert_path = os.path.join(cert_dir, 'cert.crt')
 
811
        key_path = os.path.join(cert_dir, 'cert.key')
 
812
 
 
813
        if os.path.exists(cert_path):
 
814
            os.unlink(cert_path)
 
815
        if os.path.exists(key_path):
 
816
            os.unlink(key_path)
 
817
 
 
818
        # create a key pair
 
819
        key = crypto.PKey()
 
820
        key.generate_key(crypto.TYPE_RSA, 1024)
 
821
 
 
822
        # create a self-signed cert
 
823
        cert = crypto.X509()
 
824
        cert.get_subject().C = cert_details['country_name']
 
825
        cert.get_subject().ST = cert_details['state_name']
 
826
        cert.get_subject().L = cert_details['locality_name']
 
827
        cert.get_subject().O = cert_details['organization']
 
828
        cert.get_subject().OU = cert_details['unit']
 
829
        cert.get_subject().CN = cert_details['common_name']
 
830
        cert.set_serial_number(1000)
 
831
        cert.gmtime_adj_notBefore(0)
 
832
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
 
833
        cert.set_issuer(cert.get_subject())
 
834
        cert.set_pubkey(key)
 
835
        cert.sign(key, 'sha1')
 
836
 
 
837
        with open(cert_path, 'wt') as fd:
 
838
            fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
 
839
 
 
840
        with open(key_path, 'wt') as fd:
 
841
            fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
 
842
 
 
843
        return dict(key=key_path, cert=cert_path)
 
844
 
 
845
 
 
846
class CorrectProxyTestCase(BaseSSLTestCase):
 
847
    """Test the interaction with a SSL enabled proxy."""
 
848
 
 
849
    @defer.inlineCallbacks
 
850
    def setUp(self):
 
851
        """Set the tests."""
 
852
        yield super(CorrectProxyTestCase, self).setUp()
 
853
 
 
854
        # fake the gsettings to have diff settings for https and http
 
855
        http_settings = self.get_auth_proxy_settings()
 
856
 
 
857
        #remember so that we can use them in the creds request
 
858
        proxy_username = http_settings['username']
 
859
        proxy_password = http_settings['password']
 
860
 
 
861
        # delete the username and password so that we get a 407 for testing
 
862
        del http_settings['username']
 
863
        del http_settings['password']
 
864
 
 
865
        https_settings = self.get_nonauth_proxy_settings()
 
866
 
 
867
        proxy_settings = dict(http=http_settings, https=https_settings)
 
868
        self.patch(gsettings, "get_proxy_settings", lambda: proxy_settings)
 
869
 
 
870
        self.wc = webclient.webclient_factory()
 
871
        self.addCleanup(self.wc.shutdown)
 
872
 
 
873
        self.called = []
 
874
 
 
875
        def fake_creds_request(domain, retry):
 
876
            """Fake user interaction."""
 
877
            self.called.append('request_proxy_auth_credentials')
 
878
            self.wc.proxy_username = proxy_username
 
879
            self.wc.proxy_password = proxy_password
 
880
            return defer.succeed(True)
 
881
 
 
882
        self.patch(self.wc, 'request_proxy_auth_credentials',
 
883
                   fake_creds_request)
 
884
 
 
885
    def assert_header_contains(self, headers, expected):
 
886
        """One of the headers matching key must contain a given value."""
 
887
        self.assertTrue(any(expected in value for value in headers))
 
888
 
 
889
    def test_https_request(self):
 
890
        """Test using the correct proxy for the ssl request.
 
891
 
 
892
        In order to assert that the correct proxy is used we expect not to call
 
893
        the auth dialog since we set the https proxy not to use the auth proxy
 
894
        and to fail because we are reaching a https page with bad self-signed
 
895
        certs.
 
896
        """
 
897
        # we fail due to the fake ssl cert
 
898
        self.failUnlessFailure(self.wc.request(
 
899
                                     self.base_ssl_iri + SIMPLERESOURCE),
 
900
                                     webclient.WebClientError)
 
901
        # https requests do not use the auth proxy therefore called should be
 
902
        # empty. This asserts that we are using the correct settings for the
 
903
        # request.
 
904
        self.assertEqual([], self.called)
 
905
 
 
906
    @defer.inlineCallbacks
 
907
    def test_http_request(self):
 
908
        """Test using the correct proxy for the plain request.
 
909
 
 
910
        This tests does the opposite to the https tests. We did set the auth
 
911
        proxy for the http request therefore we expect the proxy dialog to be
 
912
        used and not to get an error since we are not visiting a https with bad
 
913
        self-signed certs.
 
914
        """
 
915
        # we do not fail since we are not going to the https page
 
916
        result = yield self.wc.request(self.base_iri + SIMPLERESOURCE)
 
917
        self.assert_header_contains(result.headers["Via"], "squid")
 
918
        # assert that we did go through the auth proxy
 
919
        self.assertIn('request_proxy_auth_credentials', self.called)
 
920
 
 
921
    if WEBCLIENT_MODULE_NAME.endswith(".txweb"):
 
922
        reason = 'Multiple proxy settings is not supported.'
 
923
        test_https_request.skip = reason
 
924
        test_http_request.skip = reason
 
925
 
 
926
    if WEBCLIENT_MODULE_NAME.endswith(".libsoup"):
 
927
        reason = 'Hard to test since we need to fully mock gsettings.'
 
928
        test_https_request.skip = reason
 
929
        test_http_request.skip = reason
 
930
 
 
931
 
 
932
class SSLTestCase(BaseSSLTestCase):
 
933
    """Test error handling when dealing with ssl."""
 
934
 
 
935
    @defer.inlineCallbacks
 
936
    def setUp(self):
 
937
        """Set the diff tests."""
 
938
        yield super(SSLTestCase, self).setUp()
 
939
 
 
940
        self.wc = webclient.webclient_factory()
 
941
        self.addCleanup(self.wc.shutdown)
 
942
 
 
943
        self.return_code = USER_CANCELLATION
 
944
        self.called = []
 
945
 
 
946
        def fake_launch_ssl_dialog(client, domain, details):
 
947
            """Fake the ssl dialog."""
 
948
            self.called.append(('_launch_ssl_dialog', domain, details))
 
949
            return defer.succeed(self.return_code)
 
950
 
 
951
        self.patch(BaseWebClient, '_launch_ssl_dialog', fake_launch_ssl_dialog)
 
952
 
 
953
    @defer.inlineCallbacks
 
954
    def _assert_ssl_fail_user_accepts(self, proxy_settings=None):
 
955
        """Assert the dialog is shown in an ssl fail."""
 
956
        self.return_code = USER_SUCCESS
 
957
        if proxy_settings:
 
958
            self.wc.force_use_proxy(proxy_settings)
 
959
        yield self.wc.request(self.base_ssl_iri + SIMPLERESOURCE)
 
960
        details = SSL_DETAILS_TEMPLATE % self.cert_details
 
961
        self.assertIn(('_launch_ssl_dialog', gethostname(), details),
 
962
                      self.called)
 
963
 
 
964
    def test_ssl_fail_dialog_user_accepts(self):
 
965
        """Test showing the dialog and accepting."""
 
966
        self._assert_ssl_fail_user_accepts()
 
967
 
 
968
    def test_ssl_fail_dialog_user_accepts_via_proxy(self):
 
969
        """Test showing the dialog and accepting when using a proxy."""
 
970
        self._assert_ssl_fail_user_accepts(self.get_nonauth_proxy_settings())
 
971
 
 
972
    def test_ssl_fail_dialog_user_rejects(self):
 
973
        """Test showing the dialog and rejecting."""
 
974
        self.failUnlessFailure(self.wc.request(self.base_iri + SIMPLERESOURCE),
 
975
                                     webclient.WebClientError)
 
976
 
 
977
    def test_format_ssl_details(self):
 
978
        """Assert that details are correctly formatted"""
 
979
        details = SSL_DETAILS_TEMPLATE % self.cert_details
 
980
        self.assertEqual(details,
 
981
                self.wc.format_ssl_details(self.cert_details))
 
982
 
 
983
    if (WEBCLIENT_MODULE_NAME.endswith(".txweb") or
 
984
            WEBCLIENT_MODULE_NAME.endswith(".libsoup")):
 
985
        reason = 'SSL support has not yet been implemented.'
 
986
        test_ssl_fail_dialog_user_accepts.skip = reason
 
987
        test_ssl_fail_dialog_user_accepts_via_proxy.skip = reason
 
988
        test_ssl_fail_dialog_user_rejects.skip = reason