示例#1
0
    def test_base_string_uri(self):
        """
        Example text to be turned into a normalized base string uri::

            GET /?q=1 HTTP/1.1
            Host: www.example.net:8080

        Sample string generated::

            https://www.example.net:8080/
        """

        # test first example from RFC 5849 section 3.4.1.2.
        # Note: there is a space between "r" and "v"
        uri = 'http://EXAMPLE.COM:80/r v/X?id=123'
        self.assertEqual(base_string_uri(uri),
                         'http://example.com/r%20v/X')

        # test second example from RFC 5849 section 3.4.1.2.
        uri = 'https://www.example.net:8080/?q=1'
        self.assertEqual(base_string_uri(uri),
                         'https://www.example.net:8080/')

        # test for unicode failure
        uri = b"www.example.com:8080"
        self.assertRaises(ValueError, base_string_uri, uri)

        # test for missing scheme
        uri = "www.example.com:8080"
        self.assertRaises(ValueError, base_string_uri, uri)

        # test a URI with the default port
        uri = "http://www.example.com:80/"
        self.assertEqual(base_string_uri(uri),
                         "http://www.example.com/")

        # test a URI missing a path
        uri = "http://www.example.com"
        self.assertEqual(base_string_uri(uri),
                         "http://www.example.com/")

        # test a relative URI
        uri = "/a-host-relative-uri"
        host = "www.example.com"
        self.assertRaises(ValueError, base_string_uri, (uri, host))

        # test overriding the URI's netloc with a host argument
        uri = "http://www.example.com/a-path"
        host = "alternatehost.example.com"
        self.assertEqual(base_string_uri(uri, host),
                         "http://alternatehost.example.com/a-path")
示例#2
0
    def test_base_string_uri(self):
        """
        Example text to be turned into a normalized base string uri::

            GET /?q=1 HTTP/1.1
            Host: www.example.net:8080

        Sample string generated::

            https://www.example.net:8080/
        """

        # test first example from RFC 5849 section 3.4.1.2.
        # Note: there is a space between "r" and "v"
        uri = 'http://EXAMPLE.COM:80/r v/X?id=123'
        self.assertEqual(base_string_uri(uri),
                         'http://example.com/r%20v/X')

        # test second example from RFC 5849 section 3.4.1.2.
        uri = 'https://www.example.net:8080/?q=1'
        self.assertEqual(base_string_uri(uri),
                         'https://www.example.net:8080/')

        # test for unicode failure
        uri = b"www.example.com:8080"
        self.assertRaises(ValueError, base_string_uri, uri)

        # test for missing scheme
        uri = "www.example.com:8080"
        self.assertRaises(ValueError, base_string_uri, uri)

        # test a URI with the default port
        uri = "http://www.example.com:80/"
        self.assertEqual(base_string_uri(uri),
                         "http://www.example.com/")

        # test a URI missing a path
        uri = "http://www.example.com"
        self.assertEqual(base_string_uri(uri),
                         "http://www.example.com/")

        # test a relative URI
        uri = "/a-host-relative-uri"
        host = "www.example.com"
        self.assertRaises(ValueError, base_string_uri, (uri, host))

        # test overriding the URI's netloc with a host argument
        uri = "http://www.example.com/a-path"
        host = "alternatehost.example.com"
        self.assertEqual(base_string_uri(uri, host),
                         "http://alternatehost.example.com/a-path")
示例#3
0
    def _make_lti11_basic_launch_args(
        oauth_consumer_key: str = "my_consumer_key",
        oauth_consumer_secret: str = "my_shared_secret",
    ):
        oauth_timestamp = str(int(time.time()))
        oauth_nonce = secrets.token_urlsafe(32)
        args = {
            "lti_message_type": "basic-lti-launch-request",
            "lti_version": "LTI-1p0".encode(),
            "resource_link_id": "88391-e1919-bb3456",
            "oauth_consumer_key": oauth_consumer_key,
            "oauth_timestamp": str(int(oauth_timestamp)),
            "oauth_nonce": str(oauth_nonce),
            "oauth_signature_method": "HMAC-SHA1",
            "oauth_callback": "about:blank",
            "oauth_version": "1.0",
            "user_id": "123123123",
        }
        extra_args = {"my_key": "this_value"}
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        launch_url = "http://jupyterhub/hub/lti/launch"

        args.update(extra_args)

        base_string = signature.signature_base_string(
            "POST",
            signature.base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args, headers=headers)),
        )

        args["oauth_signature"] = signature.sign_hmac_sha1(
            base_string, oauth_consumer_secret, None)
        return args
示例#4
0
    def get_context_data(self, **kwargs):
        role = self.request.GET.get('role', '')
        campus = self.request.GET.get('campus', '')

        lti_parameters = [
            ("roles", self._lti_role[role]),
            ("ext_roles", self._lti_ext_role[role]),
            ("custom_canvas_account_sis_id",
             'uwcourse:{}:arts-&-sciences:psych:psych'.format(campus)),
            ("oauth_timestamp", generate_timestamp()),
            ("oauth_nonce", generate_nonce()),
            ("resource_link_title",
             "UW LTI Development ({})".format(self.lti_app())),
        ]

        lti_parameters += self._static_lti_parameters

        # sign payload
        lti_app_uri = self.lti_app_uri()
        sbs = signature_base_string('POST', base_string_uri(lti_app_uri + '/'),
                                    normalize_parameters(lti_parameters))
        client_key = self._client_key
        client = Client(client_key,
                        client_secret=self._client_secrets[client_key])
        signature = sign_hmac_sha1_with_client(sbs, client)
        lti_parameters.append(("oauth_signature", signature))

        context = super().get_context_data(**kwargs)
        context['uri'] = lti_app_uri
        context['campus'] = campus
        context['role_name'] = role
        context['lti_parameters'] = lti_parameters
        return context
示例#5
0
    def _make_lti11_basic_launch_args(
        oauth_consumer_key: str = 'my_consumer_key',
        oauth_consumer_secret: str = 'my_shared_secret',
    ):
        oauth_timestamp = str(int(time.time()))
        oauth_nonce = secrets.token_urlsafe(32)
        args = {
            'lti_message_type': 'basic-lti-launch-request',
            'lti_version': 'LTI-1p0'.encode(),
            'resource_link_id': '88391-e1919-bb3456',
            'oauth_consumer_key': oauth_consumer_key,
            'oauth_timestamp': str(int(oauth_timestamp)),
            'oauth_nonce': str(oauth_nonce),
            'oauth_signature_method': 'HMAC-SHA1',
            'oauth_callback': 'about:blank',
            'oauth_version': '1.0',
            'user_id': '123123123',
        }
        extra_args = {'my_key': 'this_value'}
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        launch_url = 'http://jupyterhub/hub/lti/launch'

        args.update(extra_args)

        base_string = signature.signature_base_string(
            'POST',
            signature.base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args, headers=headers)),
        )

        args['oauth_signature'] = signature.sign_hmac_sha1(
            base_string, oauth_consumer_secret, None)
        return args
示例#6
0
    def test_signature_base_string(self):
        """
        Example text to be turned into a base string::

            POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1
            Host: example.com
            Content-Type: application/x-www-form-urlencoded
            Authorization: OAuth realm="Example",
                           oauth_consumer_key="9djdj82h48djs9d2",
                           oauth_token="kkk9d7dh3k39sjv7",
                           oauth_signature_method="HMAC-SHA1",
                           oauth_timestamp="137131201",
                           oauth_nonce="7d8f3e4a",
                           oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D"
            c2&a3=2+q

        Sample Base string generated and tested against::
            POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q
            %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_
            key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m
            ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk
            9d7dh3k39sjv7
        """

        self.assertRaises(ValueError, base_string_uri, self.base_string_url)
        base_string_url = base_string_uri(self.base_string_url.decode('utf-8'))
        base_string_url = base_string_url.encode('utf-8')
        querystring = self.base_string_url.split(b'?', 1)[1]
        query_params = collect_parameters(querystring.decode('utf-8'),
                                          body=self.body)
        normalized_encoded_query_params = sorted(
            [(quote(k), quote(v)) for k, v in query_params])
        normalized_request_string = "&".join(sorted(
            ['='.join((k, v)) for k, v in (
                        self.normalized_encoded_request_params +
                        normalized_encoded_query_params)
             if k.lower() != 'oauth_signature']))
        self.assertRaises(ValueError, signature_base_string,
                          self.http_method,
                          base_string_url,
                          normalized_request_string)
        self.assertRaises(ValueError, signature_base_string,
                          self.http_method.decode('utf-8'),
                          base_string_url,
                          normalized_request_string)

        base_string = signature_base_string(
            self.http_method.decode('utf-8'),
            base_string_url.decode('utf-8'),
            normalized_request_string
        )

        self.assertEqual(self.control_base_string, base_string)
def make_args(consumer_key, consumer_secret, launch_url, oauth_timestamp,
              oauth_nonce, extra_args):
    args = {
        "oauth_consumer_key": consumer_key,
        "oauth_timestamp": str(oauth_timestamp),
        "oauth_nonce": oauth_nonce,
    }

    args.update(extra_args)

    base_string = signature.signature_base_string(
        "POST",
        signature.base_string_uri(launch_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})),
    )

    args["oauth_signature"] = signature.sign_hmac_sha1(base_string,
                                                       consumer_secret, None)

    return args
示例#8
0
def make_args(
        consumer_key, consumer_secret,
        launch_url, oauth_timestamp, oauth_nonce, extra_args
):
    args = {
        'oauth_consumer_key': consumer_key,
        'oauth_timestamp': str(oauth_timestamp),
        'oauth_nonce': oauth_nonce
    }

    args.update(extra_args)

    base_string = signature.signature_base_string(
        'POST',
        signature.base_string_uri(launch_url),
        signature.normalize_parameters(
            signature.collect_parameters(body=args, headers={})
        )
    )

    args['oauth_signature'] = signature.sign_hmac_sha1(base_string, consumer_secret, None)

    return args
示例#9
0
    def validate_launch_request(self, launch_url, headers, args):
        """
        Validate a given launch request

        launch_url: Full URL that the launch request was POSTed to
        headers: k/v pair of HTTP headers coming in with the POST
        args: dictionary of body arguments passed to the launch_url
            Must have the following keys to be valid:
                oauth_consumer_key, oauth_timestamp, oauth_nonce,
                oauth_signature
        """

        # Validate args!
        if 'oauth_consumer_key' not in args:
            raise web.HTTPError(401, "oauth_consumer_key missing")
        if args['oauth_consumer_key'] not in self.consumers:
            raise web.HTTPError(401, "oauth_consumer_key not known")

        if 'oauth_signature' not in args:
            raise web.HTTPError(401, "oauth_signature missing")
        if 'oauth_timestamp' not in args:
            raise web.HTTPError(401, 'oauth_timestamp missing')

        # Allow 30s clock skew between LTI Consumer and Provider
        # Also don't accept timestamps from before our process started, since that could be
        # a replay attack - we won't have nonce lists from back then. This would allow users
        # who can control / know when our process restarts to trivially do replay attacks.
        oauth_timestamp = int(float(args['oauth_timestamp']))
        if (int(time.time()) - oauth_timestamp > 30
                or oauth_timestamp < LTILaunchValidator.PROCESS_START_TIME):
            raise web.HTTPError(401, "oauth_timestamp too old")

        if 'oauth_nonce' not in args:
            raise web.HTTPError(401, 'oauth_nonce missing')
        if (oauth_timestamp in LTILaunchValidator.nonces
                and args['oauth_nonce']
                in LTILaunchValidator.nonces[oauth_timestamp]):
            raise web.HTTPError(401,
                                "oauth_nonce + oauth_timestamp already used")
        LTILaunchValidator.nonces.setdefault(oauth_timestamp,
                                             set()).add(args['oauth_nonce'])

        args_list = []
        for key, values in args.items():
            if type(values) is list:
                args_list += [(key, value) for value in values]
            else:
                args_list.append((key, values))

        base_string = signature.signature_base_string(
            'POST', signature.base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args_list, headers=headers)))

        consumer_secret = self.consumers[args['oauth_consumer_key']]

        sign = signature.sign_hmac_sha1(base_string, consumer_secret, None)
        is_valid = signature.safe_string_equals(sign, args['oauth_signature'])

        if not is_valid:
            raise web.HTTPError(401, "Invalid oauth_signature")

        return True
示例#10
0
    def validate_launch_request(
        self,
        launch_url: str,
        headers: Dict[str, Any],
        args: Dict[str, Any],
    ) -> bool:
        """
        Validate a given LTI 1.1 launch request. The arguments' k/v's are either
        required, recommended, or optional. The required/recommended/optional
        keys are defined as constants.

        Args:
          launch_url: URL (base_url + path) that receives the launch request,
            usually from a tool consumer.
          headers: HTTP headers included with the POST request
          args: the body sent to the launch url.

        Returns:
          True if the validation passes, False otherwise.

        Raises:
          HTTPError if a required argument is not inclued in the POST request.
        """
        # Ensure that required oauth_* body arguments are included in the request
        for param in LTI11_OAUTH_ARGS:
            if param not in args.keys():
                raise HTTPError(
                    400, f"Required oauth arg {param} not included in request")
            if not args.get(param):
                raise HTTPError(
                    400, f"Required oauth arg {param} does not have a value")

        # Ensure that consumer key is registered in in jupyterhub_config.py
        # LTI11Authenticator.consumers defined in parent class
        if args["oauth_consumer_key"] not in self.consumers:
            raise HTTPError(401, "unknown oauth_consumer_key")

        # Ensure that required LTI 1.1 body arguments are included in the request
        for param in LTI11_LAUNCH_PARAMS_REQUIRED:
            if param not in args.keys():
                raise HTTPError(
                    400,
                    f"Required LTI 1.1 arg arg {param} not included in request"
                )
            if not args.get(param):
                raise HTTPError(
                    400, f"Required LTI 1.1 arg {param} does not have a value")

        # Inspiration to validate nonces/timestamps from OAuthlib
        # https://github.com/oauthlib/oauthlib/blob/HEAD/oauthlib/oauth1/rfc5849/endpoints/base.py#L147
        if len(str(int(args["oauth_timestamp"]))) != 10:
            raise HTTPError(401, "Invalid timestamp format.")
        try:
            ts = int(args["oauth_timestamp"])
        except ValueError:
            raise HTTPError(401, "Timestamp must be an integer.")
        else:
            # Reject timestamps that are older than 30 seconds
            if abs(time.time() - ts) > 30:
                raise HTTPError(
                    401,
                    "Timestamp given is invalid, differ from "
                    f"allowed by over {int(time.time() - ts)} seconds.",
                )
            if (ts in LTI11LaunchValidator.nonces and args["oauth_nonce"]
                    in LTI11LaunchValidator.nonces[ts]):
                raise HTTPError(401,
                                "oauth_nonce + oauth_timestamp already used")
            LTI11LaunchValidator.nonces.setdefault(ts, set()).add(
                args["oauth_nonce"])

        # convert arguments dict back to a list of tuples for signature
        args_list = [(k, v) for k, v in args.items()]

        base_string = signature.signature_base_string(
            "POST",
            signature.base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args_list, headers=headers)),
        )
        consumer_secret = self.consumers[args["oauth_consumer_key"]]
        sign = signature.sign_hmac_sha1(base_string, consumer_secret, None)
        is_valid = signature.safe_string_equals(sign, args["oauth_signature"])
        self.log.debug(f"signature in request: {args['oauth_signature']}")
        self.log.debug(f"calculated signature: {sign}")
        if not is_valid:
            raise HTTPError(401, "Invalid oauth_signature")

        return True
示例#11
0
    def _make_lti11_basic_launch_args(
        roles: str = "Instructor",
        ext_roles: str = "urn:lti:instrole:ims/lis/Instructor",
        lms_vendor: str = "canvas",
        oauth_consumer_key: str = "my_consumer_key",
        oauth_consumer_secret: str = "my_shared_secret",
    ):
        oauth_timestamp = str(int(time.time()))
        oauth_nonce = secrets.token_urlsafe(32)
        args = {
            "oauth_callback": "about:blank",
            "oauth_consumer_key": oauth_consumer_key,
            "oauth_timestamp": str(int(oauth_timestamp)),
            "oauth_nonce": str(oauth_nonce),
            "oauth_signature_method": "HMAC-SHA1",
            "oauth_version": "1.0",
            "context_id": "888efe72d4bbbdf90619353bb8ab5965ccbe9b3f",
            "context_label": "Introduction to Data Science",
            "context_title": "Introduction101",
            "course_lineitems": "https://canvas.instructure.com/api/lti/courses/1/line_items",
            "custom_canvas_assignment_title": "test-assignment",
            "custom_canvas_course_id": "616",
            "custom_canvas_enrollment_state": "active",
            "custom_canvas_user_id": "1091",
            "custom_canvas_user_login_id": "*****@*****.**",
            "ext_roles": ext_roles,
            "launch_presentation_document_target": "iframe",
            "launch_presentation_height": "1000",
            "launch_presentation_locale": "en",
            "launch_presentation_return_url": "https://canvas.instructure.com/courses/161/external_content/success/external_tool_redirect",
            "launch_presentation_width": "1000",
            "lis_outcome_service_url": "http://www.imsglobal.org/developers/LTI/test/v1p1/common/tool_consumer_outcome.php?b64=MTIzNDU6OjpzZWNyZXQ=",
            "lis_person_contact_email_primary": "*****@*****.**",
            "lis_person_name_family": "Bar",
            "lis_person_name_full": "Foo Bar",
            "lis_person_name_given": "Foo",
            "lti_message_type": "basic-lti-launch-request",
            "lis_result_sourcedid": "feb-123-456-2929::28883",
            "lti_version": "LTI-1p0",
            "resource_link_id": "888efe72d4bbbdf90619353bb8ab5965ccbe9b3f",
            "resource_link_title": "Test-Assignment",
            "roles": roles,
            "tool_consumer_info_product_family_code": lms_vendor,
            "tool_consumer_info_version": "cloud",
            "tool_consumer_instance_contact_email": "*****@*****.**",
            "tool_consumer_instance_guid": "srnuz6h1U8kOMmETzoqZTJiPWzbPXIYkAUnnAJ4u:test-lms",
            "tool_consumer_instance_name": "myedutool",
            "user_id": "185d6c59731a553009ca9b59ca3a885100000",
            "user_image": "https://lms.example.com/avatar-50.png",
        }
        extra_args = {"my_key": "this_value"}
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        launch_url = "http://jupyterhub/hub/lti/launch"

        args.update(extra_args)

        base_string = signature.signature_base_string(
            "POST",
            signature.base_string_uri(launch_url),
            signature.normalize_parameters(
                signature.collect_parameters(body=args, headers=headers)
            ),
        )

        args["oauth_signature"] = signature.sign_hmac_sha1(
            base_string, oauth_consumer_secret, None
        )
        return args
示例#12
0
    def test_base_string_uri(self):
        """
        Test the ``base_string_uri`` function.
        """

        # ----------------
        # Examples from the OAuth 1.0a specification: RFC 5849.

        # First example from RFC 5849 section 3.4.1.2.
        #
        #     GET /r%20v/X?id=123 HTTP/1.1
        #     Host: EXAMPLE.COM:80
        #
        # Note: there is a space between "r" and "v"

        self.assertEqual(
            'http://example.com/r%20v/X',
            base_string_uri('http://EXAMPLE.COM:80/r v/X?id=123'))

        # Second example from RFC 5849 section 3.4.1.2.
        #
        #     GET /?q=1 HTTP/1.1
        #     Host: www.example.net:8080

        self.assertEqual(
            'https://www.example.net:8080/',
            base_string_uri('https://www.example.net:8080/?q=1'))

        # ----------------
        # Scheme: will always be in lowercase

        for uri in [
            'foobar://www.example.com',
            'FOOBAR://www.example.com',
            'Foobar://www.example.com',
            'FooBar://www.example.com',
            'fOObAR://www.example.com',
        ]:
            self.assertEqual('foobar://www.example.com/', base_string_uri(uri))

        # ----------------
        # Host: will always be in lowercase

        for uri in [
            'http://www.example.com',
            'http://WWW.EXAMPLE.COM',
            'http://www.EXAMPLE.com',
            'http://wWW.eXAMPLE.cOM',
        ]:
            self.assertEqual('http://www.example.com/', base_string_uri(uri))

        # base_string_uri has an optional host parameter that can be used to
        # override the URI's netloc (or used as the host if there is no netloc)
        # The "netloc" refers to the "hostname[:port]" part of the URI.

        self.assertEqual(
            'http://actual.example.com/',
            base_string_uri('http://IGNORE.example.com', 'ACTUAL.example.com'))

        self.assertEqual(
            'http://override.example.com/path',
            base_string_uri('http:///path', 'OVERRIDE.example.com'))

        # ----------------
        # Port: default ports always excluded; non-default ports always included

        self.assertEqual(
            "http://www.example.com/",
            base_string_uri("http://www.example.com:80/"))  # default port

        self.assertEqual(
            "https://www.example.com/",
            base_string_uri("https://www.example.com:443/"))  # default port

        self.assertEqual(
            "https://www.example.com:999/",
            base_string_uri("https://www.example.com:999/"))  # non-default port

        self.assertEqual(
            "http://www.example.com:443/",
            base_string_uri("HTTP://www.example.com:443/"))  # non-default port

        self.assertEqual(
            "https://www.example.com:80/",
            base_string_uri("HTTPS://www.example.com:80/"))  # non-default port

        self.assertEqual(
            "http://www.example.com/",
            base_string_uri("http://www.example.com:/"))  # colon but no number

        # ----------------
        # Paths

        self.assertEqual(
            'http://www.example.com/',
            base_string_uri('http://www.example.com'))  # no slash

        self.assertEqual(
            'http://www.example.com/',
            base_string_uri('http://www.example.com/'))  # with slash

        self.assertEqual(
            'http://www.example.com:8080/',
            base_string_uri('http://www.example.com:8080'))  # no slash

        self.assertEqual(
            'http://www.example.com:8080/',
            base_string_uri('http://www.example.com:8080/'))  # with slash

        self.assertEqual(
            'http://www.example.com/foo/bar',
            base_string_uri('http://www.example.com/foo/bar'))  # no slash
        self.assertEqual(
            'http://www.example.com/foo/bar/',
            base_string_uri('http://www.example.com/foo/bar/'))  # with slash

        # ----------------
        # Query parameters & fragment IDs do not appear in the base string URI

        self.assertEqual(
            'https://www.example.com/path',
            base_string_uri('https://www.example.com/path?foo=bar'))

        self.assertEqual(
            'https://www.example.com/path',
            base_string_uri('https://www.example.com/path#fragment'))

        # ----------------
        # Percent encoding
        #
        # RFC 5849 does not specify what characters are percent encoded, but in
        # one of its examples it shows spaces being percent encoded.
        # So it is assumed that spaces must be encoded, but we don't know what
        # other characters are encoded or not.

        self.assertEqual(
            'https://www.example.com/hello%20world',
            base_string_uri('https://www.example.com/hello world'))

        self.assertEqual(
            'https://www.hello%20world.com/',
            base_string_uri('https://www.hello world.com/'))

        # ----------------
        # Errors detected

        # base_string_uri expects a string
        self.assertRaises(ValueError, base_string_uri, None)
        self.assertRaises(ValueError, base_string_uri, 42)
        self.assertRaises(ValueError, base_string_uri, b'http://example.com')

        # Missing scheme is an error
        self.assertRaises(ValueError, base_string_uri, '')
        self.assertRaises(ValueError, base_string_uri, ' ')  # single space
        self.assertRaises(ValueError, base_string_uri, 'http')
        self.assertRaises(ValueError, base_string_uri, 'example.com')

        # Missing host is an error
        self.assertRaises(ValueError, base_string_uri, 'http:')
        self.assertRaises(ValueError, base_string_uri, 'http://')
        self.assertRaises(ValueError, base_string_uri, 'http://:8080')

        # Port is not a valid TCP/IP port number
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:0')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:-1')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:65536')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:3.14')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:BAD')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:NaN')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com: ')
        self.assertRaises(ValueError, base_string_uri, 'http://eg.com:42:42')