Skip to content

Poetry ignoring credentials in source url?Β #6799

Open

Description

Discussed in https://github.com/orgs/python-poetry/discussions/6629

Originally posted by keriksson-rosenqvist September 26, 2022
I am trying to set up a poetry environment which pulls a certain package from a private pypi-like registry. As I am sharing the project with others via a git repository, I want the setup to be contained within the pyproject.toml file, i.e. not rely on configs, environment variables, or keyring settings that may differ between user devices.
I have therefore added a source with the basic http credentials included in the url. The exact same url is able to install the package and it's dependencies through pip install -i https://<username:password>@[REDACTED]/pypi/simple/ my-private-package

[tool.poetry.dependencies]
my-private-package = { version = "^2.0.0", source = "my_private_package"}
...
[[tool.poetry.source]]
name = "my_private_package"
url = "https://<username:password>@[REDACTED]/pypi/simple/"
secondary = true

I've set secondary = true as that lets dependencies be installed from the normal pypi, otherwise poetry also fails to install them.

According to this issue, others have been seemingly been able to get it working with the credentials in the url, however with the setup above raises the following error for me.

$ poetry update
Updating dependencies
Resolving dependencies... (1.7s)

401 Client Error:  for url: https://[REDACTED]/pypi/download/my-private-package/2/my_private_package-2.0.0-py3-none-any.whl#sha256=[REDACTED]

Is this a bug, am I setting it up wrong, or has there been a change to the usage since the mentioned issue was able to get it to work?

Activity

  1. kristang commented on Oct 14, 2022

    @kristang

    Try running poetry update -vvv and post the output (remember to redact anything you don't want public - like your feed address)

    Does pip install -i https://[REDACTED]/pypi/simple/my-private-package fail with a 401?

  2. keriksson-rosenqvist-oqc commented on Oct 17, 2022

    @keriksson-rosenqvist-oqc
    Author

    @kristang I've made an MWE using a new hello-world package in my private pipy registry. Running poetry update -vvv gives the following trace:

    Loading configuration file [REDACTED]/.config/pypoetry/config.toml
    Loading configuration file [REDACTED]/.config/pypoetry/auth.toml
    Adding repository hello_world (https://<username:password>@[REDACTED]/hello_world/pypi/simple) and setting it as secondary
    Using virtualenv: [REDACTED]/poetry_credential_mwe/.venv
    Project environment contains an empty path in sys_path, ignoring.
    Updating dependencies
    Resolving dependencies...
       1: fact: poetry-credential-mwe is 0.1.0
       1: derived: poetry-credential-mwe
       1: fact: poetry-credential-mwe depends on hello-world (^0.1.0)
       1: selecting poetry-credential-mwe (0.1.0)
       1: derived: hello-world (>=0.1.0,<0.2.0)
    Creating new session for <username:parrword>@[REDACTED]
    [urllib3.connectionpool] Starting new HTTPS connection (1): [REDACTED]:443
    [urllib3.connectionpool] https://[REDACTED]:443 "GET [REDACTED]/hello_world/pypi/simple/hello-world/ HTTP/1.1" 200 None
    Source (hello_world): 1 packages found for hello-world >=0.1.0,<0.2.0
    [urllib3.connectionpool] https://[REDACTED]:443 "GET [REDACTED]/hello_world/pypi/simple/hello-world/ HTTP/1.1" 200 None
    Source (hello_world): Downloading wheel: hello_world-0.1.0-py3-none-any.whl
    [keyring.backend] Loading KWallet
    [keyring.backend] Loading SecretService
    [keyring.backend] Loading Windows
    [keyring.backend] Loading chainer
    [keyring.backend] Loading libsecret
    [keyring.backend] Loading macOS
    Creating new session for [REDACTED]
    [urllib3.connectionpool] Starting new HTTPS connection (1): [REDACTED]:443
    [urllib3.connectionpool] https://[REDACTED]:443 "GET [REDACTED]/pypi/download/hello-world/0.1/hello_world-0.1.0-py3-none-any.whl HTTP/1.1" 401 343
       1: Version solving took 1.522 seconds.
       1: Tried 1 solutions.
    
      ValueError
    
      Package('hello-world', '0.1') is not in list
    
      at ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/legacy_repository.py:51 in package
           47β”‚         Note that this will be cached so the subsequent operations
           48β”‚         should be much faster.
           49β”‚         """
           50β”‚         try:
        β†’  51β”‚             index = self._packages.index(Package(name, version))
           52β”‚ 
           53β”‚             return self._packages[index]
           54β”‚         except ValueError:
           55β”‚             package = super().package(name, version, extras)
    
    The following error occurred when trying to handle this error:
    
    
      Stack trace:
    
      31  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/application.py:329 in run
           327β”‚ 
           328β”‚             try:
         β†’ 329β”‚                 exit_code = self._run(io)
           330β”‚             except Exception as e:
           331β”‚                 if not self._catch_exceptions:
    
      30  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/console/application.py:185 in _run
           183β”‚         self._load_plugins(io)
           184β”‚ 
         β†’ 185β”‚         exit_code: int = super()._run(io)
           186β”‚         return exit_code
           187β”‚ 
    
      29  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/application.py:423 in _run
           421β”‚             io.input.set_stream(stream)
           422β”‚ 
         β†’ 423β”‚         exit_code = self._run_command(command, io)
           424β”‚         self._running_command = None
           425β”‚ 
    
      28  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/application.py:465 in _run_command
           463β”‚ 
           464β”‚         if error is not None:
         β†’ 465β”‚             raise error
           466β”‚ 
           467β”‚         return event.exit_code
    
      27  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/application.py:449 in _run_command
           447β”‚ 
           448β”‚             if event.command_should_run():
         β†’ 449β”‚                 exit_code = command.run(io)
           450β”‚             else:
           451β”‚                 exit_code = ConsoleCommandEvent.RETURN_CODE_DISABLED
    
      26  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/commands/base_command.py:119 in run
           117β”‚         io.input.validate()
           118β”‚ 
         β†’ 119β”‚         status_code = self.execute(io)
           120β”‚ 
           121β”‚         if status_code is None:
    
      25  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cleo/commands/command.py:83 in execute
            81β”‚ 
            82β”‚         try:
         β†’  83β”‚             return self.handle()
            84β”‚         except KeyboardInterrupt:
            85β”‚             return 1
    
      24  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/console/commands/update.py:54 in handle
            52β”‚         self.installer.update(True)
            53β”‚ 
         β†’  54β”‚         return self.installer.run()
            55β”‚ 
    
      23  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/installation/installer.py:114 in run
           112β”‚             self._execute_operations = False
           113β”‚ 
         β†’ 114β”‚         return self._do_install()
           115β”‚ 
           116β”‚     def dry_run(self, dry_run: bool = True) -> Installer:
    
      22  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/installation/installer.py:247 in _do_install
           245β”‚                 source_root=self._env.path.joinpath("src")
           246β”‚             ):
         β†’ 247β”‚                 ops = solver.solve(use_latest=self._whitelist).calculate_operations()
           248β”‚         else:
           249β”‚             self._io.write_line("Installing dependencies from lock file")
    
      21  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/puzzle/solver.py:73 in solve
            71β”‚         with self._provider.progress():
            72β”‚             start = time.time()
         β†’  73β”‚             packages, depths = self._solve(use_latest=use_latest)
            74β”‚             end = time.time()
            75β”‚ 
    
      20  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/puzzle/solver.py:151 in _solve
           149β”‚ 
           150β”‚         try:
         β†’ 151β”‚             result = resolve_version(
           152β”‚                 self._package, self._provider, locked=locked, use_latest=use_latest
           153β”‚             )
    
      19  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/mixology/__init__.py:24 in resolve_version
            22β”‚     solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
            23β”‚ 
         β†’  24β”‚     return solver.solve()
            25β”‚ 
    
      18  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/mixology/version_solver.py:127 in solve
           125β”‚             while next is not None:
           126β”‚                 self._propagate(next)
         β†’ 127β”‚                 next = self._choose_package_version()
           128β”‚ 
           129β”‚             return self._result()
    
      17  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/mixology/version_solver.py:446 in _choose_package_version
           444β”‚             package = locked
           445β”‚ 
         β†’ 446β”‚         package = self._provider.complete_package(package)
           447β”‚ 
           448β”‚         conflict = False
    
      16  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/puzzle/provider.py:530 in complete_package
           528β”‚                 dependency_package = DependencyPackage(
           529β”‚                     dependency,
         β†’ 530β”‚                     self._pool.package(
           531β”‚                         package.pretty_name,
           532β”‚                         package.version,
    
      15  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/pool.py:152 in package
           150β”‚ 
           151β”‚         if repository is not None and not self._ignore_repository_names:
         β†’ 152β”‚             return self.repository(repository).package(name, version, extras=extras)
           153β”‚ 
           154β”‚         for repo in self._repositories:
    
      14  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/legacy_repository.py:55 in package
            53β”‚             return self._packages[index]
            54β”‚         except ValueError:
         β†’  55β”‚             package = super().package(name, version, extras)
            56β”‚             package._source_type = "legacy"
            57β”‚             package._source_url = self._url
    
      13  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/cached.py:86 in package
            84β”‚         extras: list[str] | None = None,
            85β”‚     ) -> Package:
         β†’  86β”‚         return self.get_release_info(canonicalize_name(name), version).to_package(
            87β”‚             name=name, extras=extras
            88β”‚         )
    
      12  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/cached.py:63 in get_release_info
            61β”‚             return PackageInfo.load(self._get_release_info(name, version))
            62β”‚ 
         β†’  63β”‚         cached = self._cache.remember_forever(
            64β”‚             f"{name}:{version}", lambda: self._get_release_info(name, version)
            65β”‚         )
    
      11  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cachy/repository.py:174 in remember_forever
           172β”‚             return val
           173β”‚ 
         β†’ 174β”‚         val = value(callback)
           175β”‚ 
           176β”‚         self.forever(key, val)
    
      10  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/cachy/helpers.py:6 in value
             4β”‚ def value(val):
             5β”‚     if callable(val):
         β†’   6β”‚         return val()
             7β”‚ 
             8β”‚     return val
    
       9  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/cached.py:64 in <lambda>
            62β”‚ 
            63β”‚         cached = self._cache.remember_forever(
         β†’  64β”‚             f"{name}:{version}", lambda: self._get_release_info(name, version)
            65β”‚         )
            66β”‚ 
    
       8  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/legacy_repository.py:121 in _get_release_info
           119β”‚         yanked = page.yanked(name, version)
           120β”‚ 
         β†’ 121β”‚         return self._links_to_data(
           122β”‚             links,
           123β”‚             PackageInfo(
    
       7  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/http.py:256 in _links_to_data
           254β”‚         data.files = files
           255β”‚ 
         β†’ 256β”‚         info = self._get_info_from_urls(urls)
           257β”‚ 
           258β”‚         data.summary = info.summary
    
       6  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/http.py:193 in _get_info_from_urls
           191β”‚             # Prefer non platform specific wheels
           192β”‚             if universal_python3_wheel:
         β†’ 193β”‚                 return self._get_info_from_wheel(universal_python3_wheel)
           194β”‚ 
           195β”‚             if universal_python2_wheel:
    
       5  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/http.py:84 in _get_info_from_wheel
            82β”‚         with temporary_directory() as temp_dir:
            83β”‚             filepath = Path(temp_dir) / filename
         β†’  84β”‚             self._download(url, filepath)
            85β”‚ 
            86β”‚             return PackageInfo.from_wheel(filepath)
    
       4  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/repositories/http.py:72 in _download
            70β”‚ 
            71β”‚     def _download(self, url: str, dest: Path) -> None:
         β†’  72β”‚         return download_file(url, dest, session=self.session)
            73β”‚ 
            74β”‚     def _get_info_from_wheel(self, url: str) -> PackageInfo:
    
       3  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/utils/helpers.py:85 in download_file
            83β”‚     get = requests.get if not session else session.get
            84β”‚ 
         β†’  85β”‚     response = get(url, stream=True, timeout=REQUESTS_TIMEOUT)
            86β”‚     response.raise_for_status()
            87β”‚ 
    
       2  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/utils/authenticator.py:246 in get
           244β”‚ 
           245β”‚     def get(self, url: str, **kwargs: Any) -> requests.Response:
         β†’ 246β”‚         return self.request("get", url, **kwargs)
           247β”‚ 
           248β”‚     def post(self, url: str, **kwargs: Any) -> requests.Response:
    
       1  ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/poetry/utils/authenticator.py:232 in request
           230β”‚                 if resp.status_code not in [502, 503, 504] or is_last_attempt:
           231β”‚                     if raise_for_status:
         β†’ 232β”‚                         resp.raise_for_status()
           233β”‚                     return resp
           234β”‚ 
    
      HTTPError
    
      401 Client Error: Unauthorized for url: https://[REDACTED]/pypi/download/hello-world/0.1/hello_world-0.1.0-py3-none-any.whl#sha256=[REDACTED]
    
      at ~/.local/share/pypoetry/venv/lib/python3.9/site-packages/requests/models.py:1021 in raise_for_status
          1017β”‚                 f"{self.status_code} Server Error: {reason} for url: {self.url}"
          1018β”‚             )
          1019β”‚ 
          1020β”‚         if http_error_msg:
        β†’ 1021β”‚             raise HTTPError(http_error_msg, response=self)
          1022β”‚ 
          1023β”‚     def close(self):
          1024β”‚         """Releases the connection back to the pool. Once this method has been
          1025β”‚         called the underlying ``raw`` object must not be accessed again.
    

    Running poetry run pip install -i https://[REDACTED]/hello_world/pypi/simple hello-world without providing any user credentials also fails with a 401:

    $ poetry run pip install -i https://[REDACTED]/hello_world/pypi/simple hello-world
    Looking in indexes: https://[REDACTED]/hello_world/pypi/simple
    User for [REDACTED]: 
    WARNING: 401 Error, Credentials not correct for https://[REDACTED]/hello_world/pypi/simple/hello-world/
    ERROR: Could not find a version that satisfies the requirement hello-world (from versions: none)
    ERROR: No matching distribution found for hello-world
    User for [REDACTED]: 
    WARNING: 401 Error, Credentials not correct for https://[REDACTED]/hello_world/pypi/simple/pip/
    
  3. kristang commented on Oct 17, 2022

    @kristang

    Only thing I find a little odd is the url returned: Unauthorized for url: https://[REDACTED]/pypi/download/hello-world/0.1/hello_world-0.1.0-py3-none-any.whl#sha256=[REDACTED]

    I don't recall seeing that "/download/" part when I install from a private source. What service is providing your private feed?

    It might just be my inexperience with pypi feeds in general.

  4. keriksson-rosenqvist-oqc commented on Oct 17, 2022

    @keriksson-rosenqvist-oqc
    Author

    I am using Azure Artifacts to store my private feed. As mentioned it works fine with pip install -i on the command line when providing credentials. If I supply the -vvv flag to my pip command it does use that same url https://[REDACTED]/pypi/download/hello-world/0.1/hello_world-0.1.0-py3-none-any.whl#sha256=[REDACTED]

  5. kristang commented on Oct 17, 2022

    @kristang

    Have you tried other authentication methods? I believe Azure Artifacts supports some kind of API token - last time I checked they had a guide somewhere included on the site for your feed on how to use it.

  6. keriksson-rosenqvist-oqc commented on Oct 18, 2022

    @keriksson-rosenqvist-oqc
    Author

    @kristang That is what's being used. The tokens are used through the basic auth approach. They have no support for other authentications through pip as far as I have found, and I've been in touch with their support team regarding authentication methods.

  7. kristang commented on Oct 18, 2022

    @kristang

    According to the documentation from Microsoft you can auth with artifacts-keyring.

    You can then install with:

    pip install <package-name> --index-url https://pkgs.dev.azure.com/<your-organization-name>/<your-project-name>/_packaging/<your-feed-name>/pypi/simple

    https://learn.microsoft.com/en-us/azure/devops/artifacts/quickstarts/python-packages?view=azure-devops#use-artifacts-keyring-to-set-up-authentication

    This approach worked for me when I was testing Azure Artifacts. There is also support for using a Personal Access Token, which I assume is what you have been trying to do?

  8. keriksson-rosenqvist-oqc commented on Oct 18, 2022

    @keriksson-rosenqvist-oqc
    Author

    A keyring would not solve the problem I've defined:

    I am trying to set up a poetry environment which pulls a certain package from a private pypi-like registry. As I am sharing the project with others via a git repository, I want the setup to be contained within the pyproject.toml file, i.e. not rely on configs, environment variables, or keyring settings that may differ between user devices.

    With regards to the API token versus PAT, the documentation shows that to use the pip interface you must use a PAT. I have not found any documentation on another form of token that could be used for this.

  9. kristang commented on Oct 19, 2022

    @kristang

    Arh, sorry I completely skipped that.

    (Off-topic and sidenote: I would be careful about storing your credentials like that in your git)

  10. squirrel532 commented on Nov 1, 2022

    @squirrel532
    Contributor

    I'm facing similar problem which happened on a self-host GitLab PyPI repo too.

    On version 1.2.2, poetry lock failed, but it works on version 1.1.15.

  11. keriksson-rosenqvist-oqc commented on Nov 1, 2022

    @keriksson-rosenqvist-oqc
    Author

    I'm currently running version 1.2.1, but if this used to work in an older version I'd say it's definitely a bug that it's not working now.

  12. manulen commented on Nov 10, 2022

    @manulen

    same problem here..

    @squirrel532 with version 1.1.15 poetry lock works, but installation still fails for me.

    It's interesting that the authentication seems to work in the first step. Depending on which version I set in the pyproject.toml (e.g. ^0.4.0, <=0.4.0) it is able to figure out which package it should install including the correct download link. But then in the second step when it comes to downloading the package, the authentication fails.

  13. keriksson-rosenqvist-oqc commented on Dec 8, 2022

    @keriksson-rosenqvist-oqc
    Author

    Is there any further update on this? Can it be tagged as a bug please?

  14. squirrel532 commented on Jan 21, 2023

    @squirrel532
    Contributor

    As a workaround, I configured pip with .netrc, which contains the credentials of my private repo.

    https://pip.pypa.io/en/stable/topics/authentication/#netrc-support

  15. 9 remaining items

  16. squirrel532 commented on Feb 7, 2023

    @squirrel532
    Contributor

    @bfontaine I think it is risky that someone places their credentials in pyproject.toml. Maybe we just places a warning in document to prohibit this behavior ?

    CC @sdispater

  17. bfontaine commented on Feb 7, 2023

    @bfontaine

    I don’t see any technical reason this shouldn’t work. Right now this is a bug: the URL including credentials is perfectly valid but Poetry appears to silently strip the credentials.

    It’s risky for public packages, but there are valid use-cases in a private/company environment. See what @keriksson-rosenqvist wrote in their initial message:

    I want the setup to be contained within the pyproject.toml file, i.e. not rely on configs, environment variables, or keyring settings that may differ between user devices.

  18. Strice91 commented on Feb 7, 2023

    @Strice91

    Hi! I also experience this issue. Thanks to @squirrel532 for this work around. So we can keep our internal projects going.

    I think, people should be careful with their credentials and think twice before putting them anywhere. But in our internal projects at work it is quite useful to checkout private repos which are dependent on each other.

    So if I understood it correctly the bug is still there but will not be fixed as the issue was closed? πŸ€”

  19. keriksson-rosenqvist-oqc commented on Feb 8, 2023

    @keriksson-rosenqvist-oqc
    Author

    Since there seems to be more people experiencing this issue, I've reopened it in hopes the bug will be looked at in the future.

  20. clintonroy commented on Feb 8, 2023

    @clintonroy
    Contributor

    I cannot imagine the project will ever accept credentials in pyproject.toml, poetry has no idea where that pyproject.toml will be published, in public or private. The first project that accidentally publishes credentials in pyproject.toml will end up with a security bug against poetry.

  21. bfontaine commented on Feb 9, 2023

    @bfontaine

    @clintonroy The question is not if the project accepts credentials because it doesn’t have a choice: URLs that contain credentials are valid HTTP URLs that shouldn’t be dealt with in a different manner than others. The suggested solution is to put the credentials in poetry.toml, which you can put in git as well. I don’t remember seeing a security bug against SSH because someone put their private key in a public Git repository.

  22. fannigurt commented on Mar 20, 2023

    @fannigurt

    Hi!, thanks a lot for the instructions above!
    Just wanted to mention, that we can add credentials without creating additional poetry.toml locally.

    Here is the config command:

    poetry config http-basic.your_repo_alias "username" "your_token"

    So such steps need:

    1. Add credentials as mentioned

    2. pyproject.toml edit like squirrel532 provided above:

    [tool.poetry.dependencies]
    you_awesome_package = { source = "your_repo_alias", version = "^X.X.X" }
    
    [[tool.poetry.source]]
    name = "your_repo_alias"
    url = "https://cloud-or-own-gitlab.tld/api/v4/projects/<PROJECT_ID_HERE>/packages/pypi/simple"
    secondary = true

    Use poetry add you_awesome_package or poetry update you_awesome_package as usual.

  23. bfontaine commented on Mar 20, 2023

    @bfontaine

    @fannigurt yes, but this issue is specifically about adding credentials inside the repository in order to share them with everyone. By default poetry config edits your user config. To edit the local config, you can use poetry config --local and get the same result as the workaround mentioned earlier.

  24. amp-matthew-wallace commented on Aug 1, 2023

    @amp-matthew-wallace

    This issue is still present in Poetry 1.5.1, and I believe that I've gotten to the bottom of it.

    In my testing with Google Artifact Registry (GAR), I'm seeing that the simple repository base URL is returning href values with absolute URLs. In my testing, if relative URLs are returned, there is no issue. I believe that it is likely that everyone experiencing this issue is using a repository which returns absolute URLs.

    <!DOCTYPE html>
    <html>
     <head>
      <title> Links for [REDACTED] </title>
     </head>
     <body>
      <h1> Links for [REDACTED] </h1>
    	
    	<a href="https://us-east1-python.pkg.dev/[REDACTED]-py3-none-any.whl#sha256=deadbeef">[REDACTED]-py3-none-any.whl</a><br/>
    	
    	<a href="https://us-east1-python.pkg.dev/[REDACTED].tar.gz#sha256=feedface">[REDACTED].tar.gz</a><br/>
    	
     </body>
    </html>

    PEP 503 states:

    URLs may be either absolute or relative as long as they point to the correct location.

    So there's nothing invalid about GAR's response.

    However, within poetry, this does not play nicely with the HTTP basic authentication credentials. When poetry processes these responses, urllib.parse.urljoin is used to combine the source's authenticated URL with the non-authenticated URLs from the HTML above.

    urllib.parse.urljoin(self._base_url or self._url, href)

    urljoin drops the credentials when joining the two URLs.

    import urllib.parse
    url='https://username:password@us-east1-python.pkg.dev/path/to/repo/'
    href='https://us-east1-python.pkg.dev/path/to/repo/[REDACTED].tar.gz#sha256=feedface'
    print(urllib.parse.urljoin(url, href))

    Produces: https://us-east1-python.pkg.dev/path/to/repo/[REDACTED].tar.gz#sha256=feedface

    7486 is perhaps the best solution here, as it would intercept the credentials into Poetry's authentication system early on before they have a chance to get mangled.

    However, there are some other potential risks caused by the same issue. For example, a user could be connecting to a repository via a proxy, and the returned absolute path would override the proxy URL.

    import urllib.parse
    url='https://username:password@my.proxy.com/path/to/repo/'
    href='https://us-east1-python.pkg.dev/path/to/repo/[REDACTED].tar.gz#sha256=feedface'
    print(urllib.parse.urljoin(url, href))

    Produces: https://us-east1-python.pkg.dev/path/to/repo/[REDACTED].tar.gz#sha256=feedface

  25. gresavage commented on Oct 24, 2023

    @gresavage

    TLDR: check your ~/.netrc file for conflicting credentials.

    I think I am having the same/similar issue. Unfortunately the workarounds did not work in my case. I believe in my case the error has to do with my environment (i.e. the installed packages, libraries, etc.). I'm on Ubuntu 22.04 using Poetry 1.6.1. AFAIK I haven't made any changes to poetry credential configuration between the time everything was working (~1 week ago) and now.

    I have traced my error to my system because (after a lot of troubleshooting), I found that running poetry commands (lock, update, install) in a docker container and using a secret mount to bind a local auth.toml to ~/.config/pypoetry/auth.toml in the container works to authenticate and pull the packages from the private repo. Also, putting the credentials directly into the source URL works but due to the behavior described in #6799 (comment) (as well as the obvious security risks with storing secrets in distributable code) this is not a full workaround.

    Therefore I know that a) the credentials are valid and b) on a "clean" system the process works. At some point some package or library poetry uses to authenticate must have gotten corrupted in my local system however attempts to hunt this package down have all failed - downgrading poetry, force installing python and system packages like keyring, urllib, python-dbus, libdbus, etc. have all failed to solve the problem.

    Additionally disabling the keyring entirely and either falling back to plaintext or environment variable authentication does not work either. Unfortunately, due to the nature of the problem I cannot produce a reproduction script since I have no idea which library on my system has become "corrupted". I'm hoping someone can at least point me in a direction to look.

    Any help would be greatly appreciated. In the meantime I am about to run the poetry lock command through a debugger to introspect what exact requests and metadata are being sent/received so hopefully I can isolate whether the issue lies in poetrys ability to retrieve the credentials from the keyring/plaintext/environment or a malformed request itself.

    UPDATE 2023-10-24

    I've been able to rule out poetry not getting the credentials from the keyring - it gets the credentials, encrypts them, and places them into the header. I haven't checked if the encryption is correct yet. The content of the response claims the token has expired but this doesn't make sense as the repository maintainers checked this and confirmed it was valid. Furthermore the issue occurs with my personal username and token too (I have developer access to the repository).

    Just reinstalled requests and cryptography too.

    Okay - I finally traced the error to conflicting credentials in ~/.netrc vs the keyring. Apparently, poetry will use requests to get credentials for the repository (see here) which uses ~/.netrc credentials to form the encoded authorization in the header of the request. So any changes to the configuration of the credentials using poetry config, environment variables, etc. is essentially discarded for authenticating the actual request in favor of the contents of ~/.netrc

    SO if you come across this issue and are in a situation like mine - where none of the above workarounds fixed anything - check your .netrc file for configurations matching the repository URL and either remove them or modify them so poetry uses the correct credentials.

  26. ustcxmwu commented on Jan 5, 2024

    @ustcxmwu

    I think I am having the same/similar issue. Unfortunately the workarounds did not work in my case. I believe in my case the error has to do with my environment (i.e. the installed packages, libraries, etc.). I'm on Ubuntu 22.04 using Poetry 1.6.1. AFAIK I haven't made any changes to poetry credential configuration between the time everything was working (~1 week ago) and now.

    I have traced my error to my system because (after a lot of troubleshooting), I found that running poetry commands (lock, update, install) in a docker container and using a secret mount to bind a local auth.toml to ~/.config/pypoetry/auth.toml in the container works to authenticate and pull the packages from the private repo. Also, putting the credentials directly into the source URL works but due to the behavior described in #6799 (comment) (as well as the obvious security risks with storing secrets in distributable code) this is not a full workaround.

    Therefore I know that a) the credentials are valid and b) on a "clean" system the process works. At some point some package or library poetry uses to authenticate must have gotten corrupted in my local system however attempts to hunt this package down have all failed - downgrading poetry, force installing python and system packages like keyring, urllib, python-dbus, libdbus, etc. have all failed to solve the problem.

    Additionally disabling the keyring entirely and either falling back to plaintext or environment variable authentication does not work either. Unfortunately, due to the nature of the problem I cannot produce a reproduction script since I have no idea which library on my system has become "corrupted". I'm hoping someone can at least point me in a direction to look.

    Any help would be greatly appreciated. In the meantime I am about to run the poetry lock command through a debugger to introspect what exact requests and metadata are being sent/received so hopefully I can isolate whether the issue lies in poetrys ability to retrieve the credentials from the keyring/plaintext/environment or a malformed request itself.

    UPDATE 2024-10-24

    I've been able to rule out poetry not getting the credentials from the keyring - it gets the credentials, encrypts them, and places them into the header. I haven't checked if the encryption is correct yet. The content of the response claims the token has expired but this doesn't make sense as the repository maintainers checked this and confirmed it was valid. Furthermore the issue occurs with my personal username and token too (I have developer access to the repository).

    Just reinstalled requests and cryptography too.

    Okay - I finally traced the error to conflicting credentials in ~/.netrc vs the keyring. Apparently, poetry will use requests to get credentials for the repository (see here) which uses ~/.netrc credentials to form the encoded authorization in the header of the request. So any changes to the configuration of the credentials using poetry config, environment variables, etc. is essentially discarded for authenticating the actual request in favor of the contents of ~/.netrc

    SO if you come across this issue and are in a situation like mine - where none of the above workarounds fixed anything - check your .netrc file for configurations matching the repository URL and either remove them or modify them so poetry uses the correct credentials.

    It is 2023-10-24, not 2024

  27. waryak commented on Feb 2, 2024

    @waryak

    I spent the day trying to figure out how to circumvent this issue. In the end, the only thing that works is using a git source:

    Instead of:

    [tool.poetry.dependencies]
    mydep = { source = "gitlab", version = "0.1.0" }
    
    [[tool.poetry.source]]
    name = "gitlab"
    url = "https://username:password@gitlab.com/api/v4/projects/1234/packages/pypi/simple/"
    secondary = true

    I used:

    [tool.poetry.dependencies]
    mydep = { git = "https://username:password@gitlab.com/myorg/mydep.git", tag = "0.1.0" }

    This completely bypass the pypi server but there seems to be no other choice while this issue is not fixed.

    woow, if you create default username for token, which is gitlab+deploy-token-{n}, poetry will consider it as a wrong git url. I advice to create simple usernames like user. Thanks for an answer, that helped!

  28. kristianeschenburg commented on Jun 11, 2024

    @kristianeschenburg

    I was having this same issue. Turns out I had my Gitlab credentials stored in the .netrc file as well, so @gresavage solution solved the failed authorization issues for me.

  29. emocibob commented on Jul 26, 2024

    @emocibob

    I had a similar issue with GitLab. Since the package I wanted to install was in another repo in GitLab, I had to go to that repo's Settings > CI/CD and add my other repo (the one where the pipelines run) under Token Access > Limit access to this project > Groups and projects with access. More info.

    After that, I could use Poetry to add my private package with a pipeline job token.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/questionUser questions (candidates for conversion to discussion)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions