mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-05 21:12:06 +00:00
fix(3cx): ignore a client secret this process already replaced
A slower caller still holding the pre-rotation secret could write it back over the new one. The swap now happens under the auth lock, and a retired secret is dropped.
This commit is contained in:
parent
c6cb6b5a4c
commit
dccf366dcf
2 changed files with 27 additions and 9 deletions
|
|
@ -47,6 +47,7 @@ class ThreeCXClient:
|
||||||
self._auth_mode = auth_mode
|
self._auth_mode = auth_mode
|
||||||
self._client_id = auth_kwargs.get("client_id")
|
self._client_id = auth_kwargs.get("client_id")
|
||||||
self._client_secret = auth_kwargs.get("client_secret")
|
self._client_secret = auth_kwargs.get("client_secret")
|
||||||
|
self._retired_secrets: set[str] = set()
|
||||||
self._token_expires_at = 0.0
|
self._token_expires_at = 0.0
|
||||||
self._auth_lock = threading.RLock()
|
self._auth_lock = threading.RLock()
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
|
|
@ -65,12 +66,22 @@ class ThreeCXClient:
|
||||||
self._authenticate_user(auth_kwargs["username"], auth_kwargs["password"])
|
self._authenticate_user(auth_kwargs["username"], auth_kwargs["password"])
|
||||||
|
|
||||||
def use_client_secret(self, client_secret: str) -> None:
|
def use_client_secret(self, client_secret: str) -> None:
|
||||||
"""Point this client at ``client_secret`` and log in again if needed."""
|
"""Point this client at ``client_secret`` and log in again if needed.
|
||||||
if client_secret != self._client_secret:
|
|
||||||
with self._auth_lock:
|
The compare-and-set runs under ``_auth_lock``. A secret this client has
|
||||||
|
already replaced is ignored, so a slower caller still holding the
|
||||||
|
pre-rotation secret cannot write it back.
|
||||||
|
"""
|
||||||
|
with self._auth_lock:
|
||||||
|
if (
|
||||||
|
client_secret != self._client_secret
|
||||||
|
and client_secret not in self._retired_secrets
|
||||||
|
):
|
||||||
|
if self._client_secret:
|
||||||
|
self._retired_secrets.add(self._client_secret)
|
||||||
self._client_secret = client_secret
|
self._client_secret = client_secret
|
||||||
self._token_expires_at = 0.0
|
self._token_expires_at = 0.0
|
||||||
self.ensure_fresh_token()
|
self._refresh_expired_token()
|
||||||
|
|
||||||
def ensure_fresh_token(self) -> None:
|
def ensure_fresh_token(self) -> None:
|
||||||
"""Fetch a new access token when the current one is missing or near expiry."""
|
"""Fetch a new access token when the current one is missing or near expiry."""
|
||||||
|
|
@ -79,9 +90,15 @@ class ThreeCXClient:
|
||||||
if time.monotonic() < self._token_expires_at:
|
if time.monotonic() < self._token_expires_at:
|
||||||
return
|
return
|
||||||
with self._auth_lock:
|
with self._auth_lock:
|
||||||
if time.monotonic() < self._token_expires_at:
|
self._refresh_expired_token()
|
||||||
return
|
|
||||||
self._authenticate_oauth(self._client_id, self._client_secret)
|
def _refresh_expired_token(self) -> None:
|
||||||
|
"""Log in again when the token is due. Caller holds ``_auth_lock``."""
|
||||||
|
if self._auth_mode != "oauth":
|
||||||
|
return
|
||||||
|
if time.monotonic() < self._token_expires_at:
|
||||||
|
return
|
||||||
|
self._authenticate_oauth(self._client_id, self._client_secret)
|
||||||
|
|
||||||
def _request(self, method, url, **kwargs):
|
def _request(self, method, url, **kwargs):
|
||||||
if self._auth_mode == "oauth":
|
if self._auth_mode == "oauth":
|
||||||
|
|
|
||||||
|
|
@ -286,8 +286,9 @@ def test_oauth_client_reauths_when_client_secret_changes():
|
||||||
)
|
)
|
||||||
first = tcx.oauth_client("test.3cx.us", "cid", "old-secret")
|
first = tcx.oauth_client("test.3cx.us", "cid", "old-secret")
|
||||||
second = tcx.oauth_client("test.3cx.us", "cid", "new-secret")
|
second = tcx.oauth_client("test.3cx.us", "cid", "new-secret")
|
||||||
assert first is second
|
stale = tcx.oauth_client("test.3cx.us", "cid", "old-secret")
|
||||||
assert second.session.headers["Authorization"] == "Bearer tok-new"
|
assert first is second is stale
|
||||||
|
assert stale.session.headers["Authorization"] == "Bearer tok-new"
|
||||||
posts = _token_posts()
|
posts = _token_posts()
|
||||||
assert len(posts) == 2
|
assert len(posts) == 2
|
||||||
assert "client_secret=new-secret" in posts[1].request.body
|
assert "client_secret=new-secret" in posts[1].request.body
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue