Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth1: Allow IPv6 addresses being parsed by signature #818

Merged
merged 5 commits into from Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 38 additions & 28 deletions oauthlib/oauth1/rfc5849/signature.py
Expand Up @@ -37,6 +37,7 @@
import binascii
import hashlib
import hmac
import ipaddress
import logging
import urllib.parse as urlparse
import warnings
Expand Down Expand Up @@ -130,7 +131,12 @@ def base_string_uri(uri: str, host: str = None) -> str:
raise ValueError('uri must be a string.')

# FIXME: urlparse does not support unicode
scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
output = urlparse.urlparse(uri)
scheme = output.scheme
hostname = output.hostname
port = output.port
path = output.path
params = output.params

# The scheme, authority, and path of the request resource URI `RFC3986`
# are included by constructing an "http" or "https" URI representing
Expand All @@ -152,13 +158,22 @@ def base_string_uri(uri: str, host: str = None) -> str:

# 1. The scheme and host MUST be in lowercase.
scheme = scheme.lower()
netloc = netloc.lower()
# Note: if ``host`` is used, it will be converted to lowercase below
if hostname is not None:
hostname = hostname.lower()

# 2. The host and port values MUST match the content of the HTTP
# request "Host" header field.
if host is not None:
netloc = host.lower() # override value in uri with provided host
# NOTE: override value in uri with provided host
# Host argument is equal to netloc. It means it's missing scheme.
# Add it back, before parsing.

host = host.lower()
host = f"{scheme}://{host}"
output = urlparse.urlparse(host)
hostname = output.hostname
port = output.port

# 3. The port MUST be included if it is not the default port for the
# scheme, and MUST be excluded if it is the default. Specifically,
Expand All @@ -169,33 +184,28 @@ def base_string_uri(uri: str, host: str = None) -> str:
# .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
# .. _`RFC2818`: https://tools.ietf.org/html/rfc2818

if ':' in netloc:
# Contains a colon ":", so try to parse as "host:port"

hostname, port_str = netloc.split(':', 1)

if len(hostname) == 0:
raise ValueError('missing host') # error: netloc was ":port" or ":"
if hostname is None:
raise ValueError('missing host')

if len(port_str) == 0:
netloc = hostname # was "host:", so just use the host part
else:
try:
port_num = int(port_str) # try to parse into an integer number
except ValueError:
raise ValueError('port is not an integer')

if port_num <= 0 or 65535 < port_num:
raise ValueError('port out of range') # 16-bit unsigned ints
if (scheme, port_num) in (('http', 80), ('https', 443)):
netloc = hostname # default port for scheme: exclude port num
else:
netloc = hostname + ':' + str(port_num) # use hostname:port
# NOTE: Try guessing if we're dealing with IP or hostname
try:
hostname = ipaddress.ip_address(hostname)
except ValueError:
pass

if isinstance(hostname, ipaddress.IPv6Address):
hostname = f"[{hostname}]"
elif isinstance(hostname, ipaddress.IPv4Address):
hostname = f"{hostname}"

if port is not None and not (0 < port <= 65535):
raise ValueError('port out of range') # 16-bit unsigned ints
if (scheme, port) in (('http', 80), ('https', 443)):
netloc = hostname # default port for scheme: exclude port num
elif port:
netloc = f"{hostname}:{port}" # use hostname:port
else:
# Does not contain a colon, so entire value must be the hostname

if len(netloc) == 0:
raise ValueError('missing host') # error: netloc was empty string
netloc = hostname

v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))

Expand Down
20 changes: 20 additions & 0 deletions tests/oauth1/rfc5849/test_signatures.py
Expand Up @@ -228,6 +228,26 @@ def test_base_string_uri(self):
'http://override.example.com/path',
base_string_uri('http:///path', 'OVERRIDE.example.com'))

# ----------------
# Host: valid host allows for IPv4 and IPv6

self.assertEqual(
'https://192.168.0.1/',
base_string_uri('https://192.168.0.1')
)
self.assertEqual(
'https://192.168.0.1:13000/',
base_string_uri('https://192.168.0.1:13000')
)
self.assertEqual(
'https://[123:db8:fd00:1000::5]:13000/',
base_string_uri('https://[123:db8:fd00:1000::5]:13000')
)
self.assertEqual(
'https://[123:db8:fd00:1000::5]/',
base_string_uri('https://[123:db8:fd00:1000::5]')
)

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

Expand Down