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

source credential scope #16425

Merged
merged 4 commits into from
Jun 13, 2024
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
2 changes: 1 addition & 1 deletion conan/api/subapi/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def upload_backup_sources(self, files):

app = ConanApp(self.conan_api)
# TODO: verify might need a config to force it to False
uploader = FileUploader(app.requester, verify=True, config=config)
uploader = FileUploader(app.requester, verify=True, config=config, source_credentials=True)
# TODO: For Artifactory, we can list all files once and check from there instead
# of 1 request per file, but this is more general
for file in files:
Expand Down
1 change: 1 addition & 0 deletions conan/test/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def _prepare_call(self, url, kwargs):
kwargs["expect_errors"] = True
kwargs.pop("stream", None)
kwargs.pop("verify", None)
kwargs.pop("source_credentials", None)
auth = kwargs.pop("auth", None)
if auth and isinstance(auth, tuple):
app.set_authorization(("Basic", auth))
Expand Down
2 changes: 1 addition & 1 deletion conans/client/conf/config_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _process_download(config, cache_folder, requester):
filename = os.path.basename(path)
zippath = os.path.join(tmp_folder, filename)
try:
downloader = FileDownloader(requester=requester)
downloader = FileDownloader(requester=requester, source_credentials=True)
downloader.download(url=config.uri, file_path=zippath, verify_ssl=config.verify_ssl,
retry=1)
_process_zip_file(config, zippath, cache_folder, tmp_folder, first_remove=True)
Expand Down
3 changes: 2 additions & 1 deletion conans/client/downloaders/caching_file_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class SourcesCachingDownloader:
def __init__(self, conanfile):
helpers = getattr(conanfile, "_conan_helpers")
self._global_conf = helpers.global_conf
self._file_downloader = FileDownloader(helpers.requester, scope=conanfile.display_name)
self._file_downloader = FileDownloader(helpers.requester, scope=conanfile.display_name,
source_credentials=True)
self._home_folder = helpers.home_folder
self._output = conanfile.output
self._conanfile = conanfile
Expand Down
6 changes: 4 additions & 2 deletions conans/client/downloaders/file_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@

class FileDownloader:

def __init__(self, requester, scope=None):
def __init__(self, requester, scope=None, source_credentials=None):
self._output = ConanOutput(scope=scope)
self._requester = requester
self._source_credentials = source_credentials

def download(self, url, file_path, retry=2, retry_wait=0, verify_ssl=True, auth=None,
overwrite=False, headers=None, md5=None, sha1=None, sha256=None):
Expand Down Expand Up @@ -74,7 +75,8 @@ def _download_file(self, url, auth, headers, file_path, verify_ssl, try_resume=F

try:
response = self._requester.get(url, stream=True, verify=verify_ssl, auth=auth,
headers=headers)
headers=headers,
source_credentials=self._source_credentials)
except Exception as exc:
raise ConanException("Error downloading file %s: '%s'" % (url, exc))

Expand Down
24 changes: 14 additions & 10 deletions conans/client/rest/conan_requester.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
INFINITE_TIMEOUT = -1


class URLCredentials:
class _SourceURLCredentials:
"""
Only for sources download (get(), download(), conan config install
"""
def __init__(self, cache_folder):
self._urls = {}
if not cache_folder:
Expand Down Expand Up @@ -70,7 +73,7 @@ def add_auth(self, url, kwargs):
break


class ConanRequester(object):
class ConanRequester:

def __init__(self, config, cache_folder=None):
# TODO: Make all this lazy, to avoid fully configuring Requester, for every api call
Expand All @@ -84,14 +87,18 @@ def __init__(self, config, cache_folder=None):
else:
self._http_requester = requests

self._url_creds = URLCredentials(cache_folder)
self._url_creds = _SourceURLCredentials(cache_folder)
self._timeout = config.get("core.net.http:timeout", default=DEFAULT_TIMEOUT)
self._no_proxy_match = config.get("core.net.http:no_proxy_match", check_type=list)
self._proxies = config.get("core.net.http:proxies")
self._cacert_path = config.get("core.net.http:cacert_path", check_type=str)
self._client_certificates = config.get("core.net.http:client_cert")
self._clean_system_proxy = config.get("core.net.http:clean_system_proxy", default=False,
check_type=bool)
platform_info = "; ".join([" ".join([platform.system(), platform.release()]),
"Python " + platform.python_version(),
platform.machine()])
self._user_agent = "Conan/%s (%s)" % (client_version, platform_info)

@staticmethod
def _get_retries(config):
Expand Down Expand Up @@ -123,6 +130,7 @@ def _should_skip_proxy(self, url):
def _add_kwargs(self, url, kwargs):
# verify is the kwargs that comes from caller, RestAPI, it is defined in
# Conan remote "verify_ssl"
source_credentials = kwargs.pop("source_credentials", None)
if kwargs.get("verify", None) is not False: # False means de-activate
if self._cacert_path is not None:
kwargs["verify"] = self._cacert_path
Expand All @@ -135,16 +143,12 @@ def _add_kwargs(self, url, kwargs):
if not kwargs.get("headers"):
kwargs["headers"] = {}

self._url_creds.add_auth(url, kwargs)
if source_credentials:
self._url_creds.add_auth(url, kwargs)

# Only set User-Agent if none was provided
if not kwargs["headers"].get("User-Agent"):
platform_info = "; ".join([
" ".join([platform.system(), platform.release()]),
"Python "+platform.python_version(),
platform.machine()])
user_agent = "Conan/%s (%s)" % (client_version, platform_info)
kwargs["headers"]["User-Agent"] = user_agent
kwargs["headers"]["User-Agent"] = self._user_agent

return kwargs

Expand Down
11 changes: 7 additions & 4 deletions conans/client/rest/file_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

class FileUploader(object):

def __init__(self, requester, verify, config):
def __init__(self, requester, verify, config, source_credentials=None):
self._output = ConanOutput()
self._requester = requester
self._config = config
self._verify_ssl = verify
self._source_credentials = source_credentials

@staticmethod
def _handle_400_response(response, auth):
Expand All @@ -37,7 +38,7 @@ def _dedup(self, url, headers, auth):
if headers:
dedup_headers.update(headers)
response = self._requester.put(url, data="", verify=self._verify_ssl, headers=dedup_headers,
auth=auth)
auth=auth, source_credentials=self._source_credentials)
if response.status_code == 500:
raise InternalErrorException(response_to_str(response))

Expand All @@ -47,7 +48,8 @@ def _dedup(self, url, headers, auth):
return response

def exists(self, url, auth):
response = self._requester.head(url, verify=self._verify_ssl, auth=auth)
response = self._requester.head(url, verify=self._verify_ssl, auth=auth,
source_credentials=self._source_credentials)
return bool(response.ok)

def upload(self, url, abs_path, auth=None, dedup=False, retry=None, retry_wait=None,
Expand Down Expand Up @@ -84,7 +86,8 @@ def _upload_file(self, url, abs_path, headers, auth):
with open(abs_path, mode='rb') as file_handler:
try:
response = self._requester.put(url, data=file_handler, verify=self._verify_ssl,
headers=headers, auth=auth)
headers=headers, auth=auth,
source_credentials=self._source_credentials)
self._handle_400_response(response, auth)
response.raise_for_status() # Raise HTTPError for bad http response status
return response
Expand Down
20 changes: 19 additions & 1 deletion test/integration/test_source_download_password.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import json
import os
import textwrap
from unittest import mock


from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.file_server import TestFileServer
from conan.test.utils.tools import TestClient
from conans.util.files import save
Expand Down Expand Up @@ -59,3 +60,20 @@ def source(self):
c.run("source .", assert_error=True)
assert "ERROR: conanfile.py: Error in source() method, line 6" in c.out
assert "Authentication" in c.out


def test_source_credentials_only_download():
# https://github.com/conan-io/conan/issues/16396
c = TestClient(default_server_user=True)
url = c.servers["default"].fake_url

content = {"credentials": [{"url": url, "token": "password stpaces"}]}
save(os.path.join(c.cache_folder, "source_credentials.json"), json.dumps(content))

c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
c.run("create .")
# add_auth should never be called for regular upload/download
with mock.patch("conans.client.rest.conan_requester._SourceURLCredentials.add_auth", None):
c.run("upload * -c -r=default")
c.run("remove * -c")
c.run("download pkg/0.1 -r=default")