1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-11-12 23:56:13 +01:00

Add support for non-root paths in API URLs.

Currently osc supports API URLs without path only,
like https://api.example.com (if there is pathname, it's just ignored).
With this change API URLS with path, like https://example.com/api
are supported correctly.

This is useful for those who can't have OBS api, webui and main site
on different domains and buy separate X.509 certificates for them,
or just can't afford to have separate IP addresses for them
(please note that currently osc doesn't support TLS SNI).

Signed-off-by: Oleg Girko <ol@infoserver.lv>
This commit is contained in:
Oleg Girko 2013-10-26 00:15:18 +01:00 committed by Oleg Girko
parent f8f4119b88
commit ca2f1a90c8
2 changed files with 41 additions and 24 deletions

View File

@ -382,17 +382,17 @@ cookiejar = None
def parse_apisrv_url(scheme, apisrv): def parse_apisrv_url(scheme, apisrv):
if apisrv.startswith('http://') or apisrv.startswith('https://'): if apisrv.startswith('http://') or apisrv.startswith('https://'):
return urlsplit(apisrv)[0:2] return urlsplit(apisrv)[0:3]
elif scheme != None: elif scheme != None:
# the split/join is needed to get a proper url (e.g. without a trailing slash) # the split/join is needed to get a proper url (e.g. without a trailing slash)
return urlsplit(urljoin(scheme, apisrv))[0:2] return urlsplit(urljoin(scheme, apisrv))[0:3]
else: else:
msg = 'invalid apiurl \'%s\' (specify the protocol (http:// or https://))' % apisrv msg = 'invalid apiurl \'%s\' (specify the protocol (http:// or https://))' % apisrv
raise URLError(msg) raise URLError(msg)
def urljoin(scheme, apisrv): def urljoin(scheme, apisrv, path=''):
return '://'.join([scheme, apisrv]) return '://'.join([scheme, apisrv]) + path
def is_known_apiurl(url): def is_known_apiurl(url):
@ -401,6 +401,21 @@ def is_known_apiurl(url):
return apiurl in config['api_host_options'] return apiurl in config['api_host_options']
def extract_known_apiurl(url):
"""
Return longest prefix of given url that is known apiurl,
None if there is no known apiurl that is prefix of given url.
"""
scheme, host, path = parse_apisrv_url(None, url)
p = path.split('/')
while p:
apiurl = urljoin(scheme, host, '/'.join(p))
if apiurl in config['api_host_options']:
return apiurl
p.pop()
return None
def get_apiurl_api_host_options(apiurl): def get_apiurl_api_host_options(apiurl):
""" """
Returns all apihost specific options for the given apiurl, None if Returns all apihost specific options for the given apiurl, None if
@ -441,10 +456,9 @@ def get_apiurl_usr(apiurl):
# So we need to build a new opener everytime we switch the # So we need to build a new opener everytime we switch the
# apiurl (because different apiurls may have different # apiurl (because different apiurls may have different
# cafile/capath locations) # cafile/capath locations)
def _build_opener(url): def _build_opener(apiurl):
from osc.core import __version__ from osc.core import __version__
global config global config
apiurl = urljoin(*parse_apisrv_url(None, url))
if 'last_opener' not in _build_opener.__dict__: if 'last_opener' not in _build_opener.__dict__:
_build_opener.last_opener = (None, None) _build_opener.last_opener = (None, None)
if apiurl == _build_opener.last_opener[0]: if apiurl == _build_opener.last_opener[0]:
@ -644,18 +658,18 @@ def config_set_option(section, opt, val=None, delete=False, update=True, **kwarg
general_opts = [i for i in DEFAULTS.keys() if not i in ['user', 'pass', 'passx']] general_opts = [i for i in DEFAULTS.keys() if not i in ['user', 'pass', 'passx']]
if section != 'general': if section != 'general':
section = config['apiurl_aliases'].get(section, section) section = config['apiurl_aliases'].get(section, section)
scheme, host = \ scheme, host, path = \
parse_apisrv_url(config.get('scheme', 'https'), section) parse_apisrv_url(config.get('scheme', 'https'), section)
section = urljoin(scheme, host) section = urljoin(scheme, host, path)
sections = {} sections = {}
for url in cp.sections(): for url in cp.sections():
if url == 'general': if url == 'general':
sections[url] = url sections[url] = url
else: else:
scheme, host = \ scheme, host, path = \
parse_apisrv_url(config.get('scheme', 'https'), url) parse_apisrv_url(config.get('scheme', 'https'), url)
apiurl = urljoin(scheme, host) apiurl = urljoin(scheme, host, path)
sections[apiurl] = url sections[apiurl] = url
section = sections.get(section.rstrip('/'), section) section = sections.get(section.rstrip('/'), section)
@ -702,19 +716,20 @@ def write_initial_config(conffile, entries, custom_template=''):
config.update(entries) config.update(entries)
# at this point use_keyring and gnome_keyring are str objects # at this point use_keyring and gnome_keyring are str objects
if config['use_keyring'] == '1' and GENERIC_KEYRING: if config['use_keyring'] == '1' and GENERIC_KEYRING:
protocol, host = \ protocol, host, path = \
parse_apisrv_url(None, config['apiurl']) parse_apisrv_url(None, config['apiurl'])
keyring.set_password(host, config['user'], config['pass']) keyring.set_password(host, config['user'], config['pass'])
config['pass'] = '' config['pass'] = ''
config['passx'] = '' config['passx'] = ''
elif config['gnome_keyring'] == '1' and GNOME_KEYRING: elif config['gnome_keyring'] == '1' and GNOME_KEYRING:
protocol, host = \ protocol, host, path = \
parse_apisrv_url(None, config['apiurl']) parse_apisrv_url(None, config['apiurl'])
gnomekeyring.set_network_password_sync( gnomekeyring.set_network_password_sync(
user=config['user'], user=config['user'],
password=config['pass'], password=config['pass'],
protocol=protocol, protocol=protocol,
server=host) server=host,
object=path)
config['user'] = '' config['user'] = ''
config['pass'] = '' config['pass'] = ''
config['passx'] = '' config['passx'] = ''
@ -741,19 +756,20 @@ def add_section(filename, url, user, passwd):
# Section might have existed, but was empty # Section might have existed, but was empty
pass pass
if config['use_keyring'] and GENERIC_KEYRING: if config['use_keyring'] and GENERIC_KEYRING:
protocol, host = parse_apisrv_url(None, url) protocol, host, path = parse_apisrv_url(None, url)
keyring.set_password(host, user, passwd) keyring.set_password(host, user, passwd)
cp.set(url, 'keyring', '1') cp.set(url, 'keyring', '1')
cp.set(url, 'user', user) cp.set(url, 'user', user)
cp.remove_option(url, 'pass') cp.remove_option(url, 'pass')
cp.remove_option(url, 'passx') cp.remove_option(url, 'passx')
elif config['gnome_keyring'] and GNOME_KEYRING: elif config['gnome_keyring'] and GNOME_KEYRING:
protocol, host = parse_apisrv_url(None, url) protocol, host, path = parse_apisrv_url(None, url)
gnomekeyring.set_network_password_sync( gnomekeyring.set_network_password_sync(
user=user, user=user,
password=passwd, password=passwd,
protocol=protocol, protocol=protocol,
server=host) server=host,
object=path)
cp.set(url, 'keyring', '1') cp.set(url, 'keyring', '1')
cp.remove_option(url, 'pass') cp.remove_option(url, 'pass')
cp.remove_option(url, 'passx') cp.remove_option(url, 'passx')
@ -836,8 +852,8 @@ def get_config(override_conffile=None,
aliases = {} aliases = {}
for url in [x for x in cp.sections() if x != 'general']: for url in [x for x in cp.sections() if x != 'general']:
# backward compatiblity # backward compatiblity
scheme, host = parse_apisrv_url(config.get('scheme', 'https'), url) scheme, host, path = parse_apisrv_url(config.get('scheme', 'https'), url)
apiurl = urljoin(scheme, host) apiurl = urljoin(scheme, host, path)
user = None user = None
password = None password = None
if config['use_keyring'] and GENERIC_KEYRING: if config['use_keyring'] and GENERIC_KEYRING:
@ -851,7 +867,7 @@ def get_config(override_conffile=None,
elif config['gnome_keyring'] and GNOME_KEYRING: elif config['gnome_keyring'] and GNOME_KEYRING:
# Read from gnome keyring if available # Read from gnome keyring if available
try: try:
gk_data = gnomekeyring.find_network_password_sync(protocol=scheme, server=host) gk_data = gnomekeyring.find_network_password_sync(protocol=scheme, server=host, object=path)
if not 'user' in gk_data[0]: if not 'user' in gk_data[0]:
raise oscerr.ConfigError('no user found in keyring', conffile) raise oscerr.ConfigError('no user found in keyring', conffile)
user = gk_data[0]['user'] user = gk_data[0]['user']

View File

@ -3135,8 +3135,8 @@ def makeurl(baseurl, l, query=[]):
elif isinstance(query, type(dict())): elif isinstance(query, type(dict())):
query = urlencode(query) query = urlencode(query)
scheme, netloc = urlsplit(baseurl)[0:2] scheme, netloc, path = urlsplit(baseurl)[0:3]
return urlunsplit((scheme, netloc, '/'.join(l), query, '')) return urlunsplit((scheme, netloc, '/'.join([path] + l), query, ''))
def http_request(method, url, headers={}, data=None, file=None): def http_request(method, url, headers={}, data=None, file=None):
@ -3161,10 +3161,11 @@ def http_request(method, url, headers={}, data=None, file=None):
req = URLRequest(url) req = URLRequest(url)
api_host_options = {} api_host_options = {}
if conf.is_known_apiurl(url): apiurl = conf.extract_known_apiurl(url)
if apiurl is not None:
# ok no external request # ok no external request
install_opener(conf._build_opener(url)) install_opener(conf._build_opener(apiurl))
api_host_options = conf.get_apiurl_api_host_options(url) api_host_options = conf.get_apiurl_api_host_options(apiurl)
for header, value in api_host_options['http_headers']: for header, value in api_host_options['http_headers']:
req.add_header(header, value) req.add_header(header, value)