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

Add user-agent header to version check request #6143

Merged
merged 1 commit into from
May 31, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions src/tribler-core/tribler_core/modules/tests/test_versioncheck.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
import json
from asyncio import sleep
from unittest.mock import Mock

from aiohttp import web

import pytest

from tribler_core.modules import versioncheck_manager
from tribler_core.modules.versioncheck_manager import VersionCheckManager
from tribler_core.modules.versioncheck_manager import VersionCheckManager, get_user_agent_string
from tribler_core.restapi.rest_endpoint import RESTResponse
from tribler_core.version import version_id

# pylint: disable=unused-argument

# Assuming this is always a newer version id
NEW_VERSION_ID = 'v1337.0'


def make_platform_mock():
platform_mock = Mock()
platform_mock.machine = lambda: 'Something64'
platform_mock.system = lambda: 'OsName'
platform_mock.release = lambda: '123'
platform_mock.version = lambda: '123.56.67' # currently not used
platform_mock.python_version = lambda: '3.10.1'
platform_mock.architecture = lambda: ('64bit', 'FooBar') # currently only first item is used
return platform_mock

TEST_USER_AGENT = f'Tribler/{version_id} (machine=Something64; os=OsName 123; python=3.10.1; executable=64bit)'


@pytest.fixture(name='version_check_manager')
async def fixture_version_check_manager(free_port, session):
prev_platform = versioncheck_manager.platform
prev_urls = versioncheck_manager.VERSION_CHECK_URLS
versioncheck_manager.platform = make_platform_mock()
versioncheck_manager.VERSION_CHECK_URLS = [f"http://localhost:{free_port}"]
version_check_manager = VersionCheckManager(session)
yield version_check_manager
await version_check_manager.stop()
try:
yield version_check_manager
finally:
try:
await version_check_manager.stop()
finally:
versioncheck_manager.VERSION_CHECK_URLS = prev_urls
versioncheck_manager.platform = prev_platform


response = None
response_code = 200
response_lag = 0 # in seconds

last_request_user_agent = None


async def handle_version_request(_):
global response, response_code, response_lag # pylint: disable=global-statement
async def handle_version_request(request):
global response, response_code, response_lag, last_request_user_agent # pylint: disable=global-statement
if response_lag > 0:
await sleep(response_lag)
user_agent = request.headers.get('User-Agent')
last_request_user_agent = user_agent
return RESTResponse(response, status=response_code)


Expand Down Expand Up @@ -68,6 +98,15 @@ async def test_start(version_check_manager, version_server):
vcm.version_id = old_id


@pytest.mark.asyncio
async def test_user_agent(version_check_manager, version_server):
global response, last_request_user_agent # pylint: disable=global-statement
response = json.dumps({'name': 'v1.0'})
last_request_user_agent = None
await version_check_manager.check_new_version()
assert last_request_user_agent == TEST_USER_AGENT


@pytest.mark.asyncio
async def test_old_version(version_check_manager, version_server):
global response # pylint: disable=global-statement
Expand Down Expand Up @@ -165,3 +204,15 @@ async def test_fallback_on_multiple_urls(free_port, version_check_manager, versi
assert has_new_version

vcm.VERSION_CHECK_URLS = vcm_old_urls


def test_useragent_string():
platform = Mock()
platform.machine = lambda: 'AMD64'
platform.system = lambda: 'Windows'
platform.release = lambda: '10'
platform.version = lambda: '10.0.19041'
platform.python_version = lambda: '3.9.1'
platform.architecture = lambda: ('64bit', 'WindowsPE')
s = get_user_agent_string('1.2.3', platform)
assert s == 'Tribler/1.2.3 (machine=AMD64; os=Windows 10; python=3.9.1; executable=64bit)'
38 changes: 28 additions & 10 deletions src/tribler-core/tribler_core/modules/versioncheck_manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import logging
import platform
from distutils.version import LooseVersion

from aiohttp import (
Expand All @@ -23,6 +24,19 @@
VERSION_CHECK_TIMEOUT = 5 # Five seconds timeout


def get_user_agent_string(tribler_version, platform_module):
machine = platform_module.machine() # like 'AMD64'
os_name = platform_module.system() # like 'Windows'
os_release = platform_module.release() # like '10'
python_version = platform_module.python_version() # like '3.9.1'
program_achitecture = platform_module.architecture()[0] # like '64bit'

user_agent = f'Tribler/{tribler_version} ' \
f'(machine={machine}; os={os_name} {os_release}; ' \
f'python={python_version}; executable={program_achitecture})'
return user_agent


class VersionCheckManager(TaskManager):

def __init__(self, session):
Expand All @@ -41,33 +55,37 @@ async def stop(self):
async def check_new_version(self):
for version_check_url in VERSION_CHECK_URLS:
try:
if await asyncio.wait_for(self.check_new_version_api(version_check_url), VERSION_CHECK_TIMEOUT):
return True
result = await asyncio.wait_for(self.check_new_version_api(version_check_url), VERSION_CHECK_TIMEOUT)
if result is not None:
return result
except asyncio.TimeoutError:
self._logger.warning("Checking for new version failed for %s", version_check_url)
return False

async def check_new_version_api(self, version_check_url):
headers = {
'User-Agent': get_user_agent_string(version_id, platform)
}
try:
async with ClientSession(raise_for_status=True) as session:
response = await session.get(version_check_url, timeout=ClientTimeout(total=VERSION_CHECK_TIMEOUT))
response = await session.get(version_check_url, headers=headers,
timeout=ClientTimeout(total=VERSION_CHECK_TIMEOUT))
response_dict = await response.json(content_type=None)
version = response_dict['name'][1:]
if LooseVersion(version) > LooseVersion(version_id):
self.session.notifier.notify(NTFY.TRIBLER_NEW_VERSION, version)
return True
return False

except (ServerConnectionError, ClientConnectionError) as e:
self._logger.error("Error when performing version check request: %s", e)
return False
except ClientResponseError as e:
self._logger.warning("Got response code %s when performing version check request", e.status)
return False
except ContentTypeError:
self._logger.warning("Response was not in JSON format")
return False
except ClientResponseError as e:
self._logger.warning("Got response code %s when performing version check request", e.status)
except asyncio.TimeoutError:
self._logger.warning("Checking for new version failed for %s", version_check_url)
return False
except ValueError as ve:
raise ValueError(f"Failed to parse Tribler version response.\nError:{ve}")
raise ValueError(f"Failed to parse Tribler version response.\nError:{ve}") # pylint: disable=raise-missing-from

return None