Repository: conan-io/conan

Issue:
[Enhancement] Output of `conan config install` exposes preconfigured URL (which may include a password)
To help us debug your issue please explain:

- [X] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [X] I've specified the Conan version, operating system version and any tool that can be relevant.
- [X] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

On our CI system, the first build step is to run `conan config install` (with no arguments) to ensure the latest configuration is installed on the agent. No parameters are used because the initial configuration of that build agent already set up the URL. This is convenient because during initial configuration, we can load the HTTP password (i.e., http://foo:bar@artifactory/...) and it's not available anywhere within the build script.

However, the first output of the command is to output the exact URL, including the password. As a result, the password is exposed on the CI system's build log. 

It would be nice if the `conan config install` hid the password from the URL. Alternatively, perhaps a command line switch could be offered to put it into a "quiet mode" so that the URL is not output. This way, the password is not exposed in the build log.


Discussion:
Yes, this makes sense.
I would suggest trying to hide the password in any case, as some people might not be aware of the possible ``--quiet`` option and leak the password. Then the ``--quiet`` argument too, to make sure, just in case something goes wrong with the password hiding. 

Pattern to cover:
- http://username:password@example.com/
- Any other one? Git doesn't support password in git urls
Absolutely we should by default be "quiet" and then maybe allow a --quiet=false for when you need to see things (if we allow this at all.)
Well, there is a second derivative we want to consider. The URL is cached in conan.conf, for later reuse, so ``conan config install`` without arguments will reuse such URL.
Maybe you don't want to store that URL either. But we can't make this the default, as it would be breaking current behavior.

Resolving patch:
diff --git a/conans/client/conf/config_installer.py b/conans/client/conf/config_installer.py
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -1,12 +1,25 @@
 import os
-from conans.tools import unzip
 import shutil
+from six.moves.urllib.parse import urlparse
+
+from conans.tools import unzip
 from conans.util.files import rmdir, mkdir
 from conans.client.remote_registry import RemoteRegistry
 from conans import tools
 from conans.errors import ConanException
 
 
+def _hide_password(resource):
+    """
+    Hide password from url/file path
+
+    :param resource: string with url or file path
+    :return: resource with hidden password if present
+    """
+    password = urlparse(resource).password
+    return resource.replace(password, "<hidden>") if password else resource
+
+
 def _handle_remotes(registry_path, remote_file, output):
     registry = RemoteRegistry(registry_path, output)
     new_registry = RemoteRegistry(remote_file, output)
@@ -78,7 +91,7 @@ def _process_folder(folder, client_cache, output):
 
 
 def _process_download(item, client_cache, output, tmp_folder):
-    output.info("Trying to download  %s" % item)
+    output.info("Trying to download  %s" % _hide_password(item))
     zippath = os.path.join(tmp_folder, "config.zip")
     tools.download(item, zippath, out=output)
     _process_zip_file(zippath, client_cache, output, tmp_folder, remove=True)


Test patch:
diff --git a/conans/test/command/config_install_test.py b/conans/test/command/config_install_test.py
--- a/conans/test/command/config_install_test.py
+++ b/conans/test/command/config_install_test.py
@@ -10,6 +10,7 @@
 from conans.client.rest.uploader_downloader import Downloader
 from conans import tools
 from conans.client.conf import ConanClientConfigParser
+from conans.client.conf.config_installer import _hide_password
 import shutil
 
 
@@ -214,3 +215,54 @@ def reinstall_error_test(self):
         error = self.client.run("config install", ignore_error=True)
         self.assertTrue(error)
         self.assertIn("Called config install without arguments", self.client.out)
+
+    def removed_credentials_from_url_unit_test(self):
+        """
+        Unit tests to remove credentials in netloc from url when using basic auth
+        # https://github.com/conan-io/conan/issues/2324
+        """
+        url_without_credentials = r"https://server.com/resource.zip"
+        url_with_credentials = r"https://test_username:test_password_123@server.com/resource.zip"
+        url_hidden_password = r"https://test_username:<hidden>@server.com/resource.zip"
+
+        # Check url is the same when not using credentials
+        self.assertEqual(_hide_password(url_without_credentials), url_without_credentials)
+
+        # Check password is hidden using url with credentials
+        self.assertEqual(_hide_password(url_with_credentials), url_hidden_password)
+
+        # Check that it works with other protocols ftp
+        ftp_with_credentials = r"ftp://test_username_ftp:test_password_321@server.com/resurce.zip"
+        ftp_hidden_password = r"ftp://test_username_ftp:<hidden>@server.com/resurce.zip"
+        self.assertEqual(_hide_password(ftp_with_credentials), ftp_hidden_password)
+
+        # Check function also works for file paths *unix/windows
+        unix_file_path = r"/tmp/test"
+        self.assertEqual(_hide_password(unix_file_path), unix_file_path)
+        windows_file_path = r"c:\windows\test"
+        self.assertEqual(_hide_password(windows_file_path), windows_file_path)
+
+        # Check works with empty string
+        self.assertEqual(_hide_password(''), '')
+
+    def remove_credentials_config_installer_test(self):
+        """ Functional test to check credentials are not displayed in output but are still present
+        in conan configuration
+        # https://github.com/conan-io/conan/issues/2324
+        """
+        fake_url_with_credentials = "http://test_user:test_password@myfakeurl.com/myconf.zip"
+        fake_url_hidden_password = "http://test_user:<hidden>@myfakeurl.com/myconf.zip"
+
+        def my_download(obj, url, filename, **kwargs):  # @UnusedVariable
+            self.assertEqual(url, fake_url_with_credentials)
+            self._create_zip(filename)
+
+        with patch.object(Downloader, 'download', new=my_download):
+            self.client.run("config install %s" % (fake_url_with_credentials,))
+
+            # Check credentials are not displayed in output
+            self.assertNotIn(fake_url_with_credentials, self.client.out)
+            self.assertIn(fake_url_hidden_password, self.client.out)
+
+            # Check credentials still stored in configuration
+            self._check(fake_url_with_credentials)