Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c76613e
Not entirely sure what im doing.
zfralish Oct 22, 2025
c9dcfb0
chore(pre-commit): linting
Oct 22, 2025
0122e39
conda import needs to be in try except
zfralish Oct 22, 2025
3877078
Merge branch 'CLI-14/ssl-config' of https://github.com/anaconda/anaco…
zfralish Oct 22, 2025
0c5fad9
chore(pre-commit): linting
Oct 22, 2025
c08c7b9
fixing a mypy issue
zfralish Oct 22, 2025
746736c
Merge branch 'CLI-14/ssl-config' of https://github.com/anaconda/anaco…
zfralish Oct 22, 2025
27f4f55
removing uneeded var
zfralish Oct 22, 2025
a50f64a
does adding this dep work?
zfralish Oct 22, 2025
f5da00c
tsk tsk shouldnt do this
zfralish Oct 22, 2025
14f10ed
cleaning up a bit
zfralish Oct 23, 2025
a703eba
fixing types
zfralish Oct 23, 2025
7d37ba1
Merge branch 'main' into CLI-14/ssl-config
zfralish Oct 27, 2025
a093107
Merge branch 'main' into CLI-14/ssl-config
zfralish Oct 28, 2025
209ae9e
oopsie
zfralish Oct 28, 2025
904ab50
standardizing the ssl_verify type
zfralish Oct 28, 2025
86264e0
chore(pre-commit): linting
Oct 28, 2025
bd0faeb
specifying exception
zfralish Oct 28, 2025
1a54c99
Merge branch 'CLI-14/ssl-config' of https://github.com/anaconda/anaco…
zfralish Oct 28, 2025
dea838a
fixing optional
zfralish Oct 28, 2025
df5294a
splitting up retrieving and settin
zfralish Oct 28, 2025
2c4b199
chore(pre-commit): linting
Oct 28, 2025
f5a079c
fixing tox mypy
zfralish Oct 28, 2025
d571eac
Merge branch 'CLI-14/ssl-config' of https://github.com/anaconda/anaco…
zfralish Oct 28, 2025
40f9635
chore(pre-commit): linting
Oct 28, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ src/*/_version.py
/tokens.json

.DS_Store

# Pyright
pyrightconfig.json
13 changes: 11 additions & 2 deletions src/anaconda_auth/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,25 @@ def _do_login(config: AnacondaAuthSite, basic: bool) -> None:
access_token = _login_with_username(config=config)
else:
access_token = _do_auth_flow(config=config)
api_key = get_api_key(access_token, config.ssl_verify, config=config)

api_key = get_api_key(
access_token,
config.ssl_verify,
config=config,
)

token_info = TokenInfo(api_key=api_key, domain=config.domain)
token_info.save()


def get_api_key(
access_token: str,
ssl_verify: bool = True,
ssl_verify: Union[str, bool] = True,
config: Optional[AnacondaAuthSite] = None,
) -> str:
if isinstance(ssl_verify, str):
ssl_verify = True

config = config or AnacondaAuthSitesConfig.load_site()

headers = {"Authorization": f"Bearer {access_token}"}
Expand Down
48 changes: 48 additions & 0 deletions src/anaconda_auth/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import TYPE_CHECKING
from typing import Any
from typing import Optional

from requests.adapters import HTTPAdapter as BaseHttpAdapter

if TYPE_CHECKING:
from ssl import SSLContext

from urllib3 import PoolManager


class _SSLContextAdapterMixin:
"""Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.

The additional argument is forwarded directly to the pool manager. This allows us
to dynamically decide what SSL store to use at runtime, which is used to implement
the optional ``truststore`` backend.
"""

def __init__(
self,
*,
ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
self._ssl_context = ssl_context
super().__init__(**kwargs)

def init_poolmanager(
self,
connections: int,
maxsize: int,
block: bool = False,
**pool_kwargs: Any,
) -> "PoolManager":
if self._ssl_context is not None:
pool_kwargs.setdefault("ssl_context", self._ssl_context)
return super().init_poolmanager( # type: ignore[misc]
connections=connections,
maxsize=maxsize,
block=block,
**pool_kwargs,
)


class HTTPAdapter(_SSLContextAdapterMixin, BaseHttpAdapter):
pass
73 changes: 72 additions & 1 deletion src/anaconda_auth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@
from hashlib import md5
from typing import Any
from typing import Dict
from typing import MutableMapping
from typing import Optional
from typing import Union
from typing import cast
from urllib.parse import urljoin

import requests
from pydantic import BaseModel
from requests import PreparedRequest
from requests import Response
from requests.auth import AuthBase

from anaconda_auth import __version__ as version
from anaconda_auth.adapters import HTTPAdapter
from anaconda_auth.config import AnacondaAuthSite
from anaconda_auth.config import AnacondaAuthSitesConfig
from anaconda_auth.exceptions import TokenExpiredError
from anaconda_auth.exceptions import TokenNotFoundError
from anaconda_auth.token import TokenInfo
from anaconda_auth.utils import get_hostname
from anaconda_cli_base.exceptions import AnacondaConfigValidationError

# VersionInfo was renamed and is deprecated in semver>=3
try:
Expand Down Expand Up @@ -51,6 +55,12 @@ def login_required(response: Response, *args: Any, **kwargs: Any) -> Response:
return response


class CondaConfig(BaseModel):
proxy_servers: Optional[MutableMapping[str, str]] = None
ssl_verify: Optional[Union[bool, str]] = None
cert: Optional[Union[str, tuple[str, str]]] = None


class BearerAuth(AuthBase):
def __init__(
self, domain: Optional[str] = None, api_key: Optional[str] = None
Expand Down Expand Up @@ -87,9 +97,10 @@ def __init__(
api_key: Optional[str] = None,
user_agent: Optional[str] = None,
api_version: Optional[str] = None,
ssl_verify: Optional[bool] = None,
ssl_verify: Optional[Union[bool, str]] = None,
extra_headers: Optional[Union[str, dict]] = None,
hash_hostname: Optional[bool] = None,
proxy_servers: Optional[MutableMapping[str, str]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should ssl_verify in this init support str?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the standard Union[bool,str] now, based on how it is in the conda code.

):
super().__init__()

Expand All @@ -116,9 +127,15 @@ def __init__(
kwargs["extra_headers"] = extra_headers
if hash_hostname is not None:
kwargs["hash_hostname"] = hash_hostname
if proxy_servers is not None:
kwargs["proxy_servers"] = proxy_servers

conda_config = self.retrieve_base_conda_ssl_config()

self.config = config.model_copy(update=kwargs)

self.configure_ssl(conda_config)

# base_url overrides domain
self._base_uri = base_uri or f"https://{self.config.domain}"
self.headers["User-Agent"] = user_agent or self._user_agent
Expand All @@ -145,6 +162,60 @@ def __init__(
self.auth = BearerAuth(domain=self.config.domain, api_key=self.config.api_key)
self.hooks["response"].append(login_required)

def configure_ssl(self, cfg: CondaConfig) -> None:
if cfg.proxy_servers and self.config.proxy_servers is None:
self.proxies = cfg.proxy_servers

ssl_context = None

if self.config.ssl_verify == "truststore" or cfg.ssl_verify == "trustore":
try:
import ssl

import truststore # type: ignore

ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
except ImportError:
raise AnacondaConfigValidationError(
"The `ssl_verify: truststore` setting is only supported on Python 3.10 or later."
)
self.verify = True
else:
self.verify = self.config.ssl_verify

# We need an http adapter

http_adapter = HTTPAdapter(ssl_context=ssl_context)

self.mount("http://", http_adapter)
self.mount("https://", http_adapter)
self.cert = cfg.cert

def retrieve_base_conda_ssl_config(self) -> CondaConfig:
conda_config = CondaConfig()
try:
from conda.base.context import context

# from conda.gateways.connection.adapters.http import HTTPAdapter

# We need to decide which takes precedence, for now im assuming conda base config.

conda_config.proxy_servers = context.proxy_servers
conda_config.ssl_verify = context.ssl_verify

if context.client_ssl_cert_key:
conda_config.cert = (
context.client_ssl_cert,
context.client_ssl_cert_key,
)
elif context.client_ssl_cert:
conda_config.cert = context.client_ssl_cert

except ImportError:
pass

return conda_config

def urljoin(self, url: str) -> str:
return urljoin(self._base_uri, url)

Expand Down
4 changes: 3 additions & 1 deletion src/anaconda_auth/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any
from typing import Dict
from typing import Literal
from typing import MutableMapping
from typing import Optional
from typing import Union
from urllib.parse import urljoin
Expand Down Expand Up @@ -43,7 +44,7 @@ class AnacondaAuthSite(BaseModel):
domain: str = "anaconda.com"
auth_domain_override: Optional[str] = None
api_key: Optional[str] = None
ssl_verify: bool = True
ssl_verify: Union[bool, str] = True
extra_headers: Optional[Union[Dict[str, str], str]] = None
client_id: str = "b4ad7f1d-c784-46b5-a9fe-106e50441f5a"
redirect_uri: str = "http://127.0.0.1:8000/auth/oidc"
Expand All @@ -53,6 +54,7 @@ class AnacondaAuthSite(BaseModel):
login_error_path: str = "/app/local-login-error"
use_unified_repo_api_key: bool = False
hash_hostname: bool = True
proxy_servers: Optional[MutableMapping[str, str]] = None

def __init__(self, **kwargs: Any):
if self.__class__ == AnacondaAuthConfig:
Expand Down