credentials.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # credentials.py -- support for git credential helpers
  2. # Copyright (C) 2022 Daniele Trifirò <daniele@iterative.ai>
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as public by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Support for git credential helpers.
  22. https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
  23. """
  24. import sys
  25. from collections.abc import Iterator
  26. from typing import Optional
  27. from urllib.parse import ParseResult, urlparse
  28. from .config import ConfigDict, SectionLike
  29. def match_urls(url: ParseResult, url_prefix: ParseResult) -> bool:
  30. base_match = (
  31. url.scheme == url_prefix.scheme
  32. and url.hostname == url_prefix.hostname
  33. and url.port == url_prefix.port
  34. )
  35. user_match = url.username == url_prefix.username if url_prefix.username else True
  36. path_match = url.path.rstrip("/").startswith(url_prefix.path.rstrip())
  37. return base_match and user_match and path_match
  38. def match_partial_url(valid_url: ParseResult, partial_url: str) -> bool:
  39. """Matches a parsed url with a partial url (no scheme/netloc)."""
  40. if "://" not in partial_url:
  41. parsed = urlparse("scheme://" + partial_url)
  42. else:
  43. parsed = urlparse(partial_url)
  44. if valid_url.scheme != parsed.scheme:
  45. return False
  46. if any(
  47. (
  48. (parsed.hostname and valid_url.hostname != parsed.hostname),
  49. (parsed.username and valid_url.username != parsed.username),
  50. (parsed.port and valid_url.port != parsed.port),
  51. (parsed.path and parsed.path.rstrip("/") != valid_url.path.rstrip("/")),
  52. ),
  53. ):
  54. return False
  55. return True
  56. def urlmatch_credential_sections(
  57. config: ConfigDict, url: Optional[str]
  58. ) -> Iterator[SectionLike]:
  59. """Returns credential sections from the config which match the given URL."""
  60. encoding = config.encoding or sys.getdefaultencoding()
  61. parsed_url = urlparse(url or "")
  62. for config_section in config.sections():
  63. if config_section[0] != b"credential":
  64. continue
  65. if len(config_section) < 2:
  66. yield config_section
  67. continue
  68. config_url = config_section[1].decode(encoding)
  69. parsed_config_url = urlparse(config_url)
  70. if parsed_config_url.scheme and parsed_config_url.netloc:
  71. is_match = match_urls(parsed_url, parsed_config_url)
  72. else:
  73. is_match = match_partial_url(parsed_url, config_url)
  74. if is_match:
  75. yield config_section