Skip to content

Commit 0c462db

Browse files
committed
Merge PR sooperset#718: Feature/Discover the cloud-id from the provided OAuth token when in BYOAccessToken
2 parents e4fc1fc + 3501834 commit 0c462db

File tree

2 files changed

+44
-49
lines changed

2 files changed

+44
-49
lines changed

src/mcp_atlassian/utils/oauth.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def exchange_code_for_tokens(self, code: str) -> bool:
143143
self.expires_at = time.time() + token_data["expires_in"]
144144

145145
# Get the cloud ID using the access token
146-
self._get_cloud_id()
146+
self.cloud_id = get_cloud_id(self.access_token)
147147

148148
# Save the tokens
149149
self._save_tokens()
@@ -229,32 +229,6 @@ def ensure_valid_token(self) -> bool:
229229
return True
230230
return self.refresh_access_token()
231231

232-
def _get_cloud_id(self) -> None:
233-
"""Get the cloud ID for the Atlassian instance.
234-
235-
This method queries the accessible resources endpoint to get the cloud ID.
236-
The cloud ID is needed for API calls with OAuth.
237-
"""
238-
if not self.access_token:
239-
logger.debug("No access token available to get cloud ID")
240-
return
241-
242-
try:
243-
headers = {"Authorization": f"Bearer {self.access_token}"}
244-
response = requests.get(CLOUD_ID_URL, headers=headers)
245-
response.raise_for_status()
246-
247-
resources = response.json()
248-
if resources and len(resources) > 0:
249-
# Use the first cloud site (most users have only one)
250-
# For users with multiple sites, they might need to specify which one to use
251-
self.cloud_id = resources[0]["id"]
252-
logger.debug(f"Found cloud ID: {self.cloud_id}")
253-
else:
254-
logger.warning("No Atlassian sites found in the response")
255-
except Exception as e:
256-
logger.error(f"Failed to get cloud ID: {e}")
257-
258232
def _get_keyring_username(self) -> str:
259233
"""Get the keyring username for storing tokens.
260234
@@ -465,15 +439,45 @@ def from_env(cls) -> Optional["BYOAccessTokenOAuthConfig"]:
465439
BYOAccessTokenOAuthConfig instance or None if required
466440
environment variables are missing.
467441
"""
468-
cloud_id = os.getenv("ATLASSIAN_OAUTH_CLOUD_ID")
442+
469443
access_token = os.getenv("ATLASSIAN_OAUTH_ACCESS_TOKEN")
444+
cloud_id = os.getenv("ATLASSIAN_OAUTH_CLOUD_ID") or get_cloud_id(access_token)
470445

471446
if not all([cloud_id, access_token]):
472447
return None
473448

474449
return cls(cloud_id=cloud_id, access_token=access_token)
475450

476451

452+
def get_cloud_id(access_token: str) -> str | None:
453+
"""Get the cloud ID for the Atlassian instance.
454+
455+
This method queries the accessible resources endpoint to get the cloud ID.
456+
The cloud ID is needed for API calls with OAuth.
457+
"""
458+
if not access_token:
459+
logger.debug("No access token provided")
460+
return None
461+
462+
try:
463+
response = requests.get(
464+
CLOUD_ID_URL, headers={"Authorization": f"Bearer {access_token}"}
465+
)
466+
response.raise_for_status()
467+
resources = response.json() or []
468+
cloud_id = resources[0].get("id") if resources else None
469+
470+
if cloud_id:
471+
logger.debug(f"Found cloud ID: {cloud_id}")
472+
else:
473+
logger.warning("No Atlassian sites found in the response")
474+
475+
return cloud_id
476+
except Exception as e:
477+
logger.error(f"Failed to get cloud ID: {e}")
478+
return None
479+
480+
477481
def get_oauth_config_from_env() -> OAuthConfig | BYOAccessTokenOAuthConfig | None:
478482
"""Get the appropriate OAuth configuration from environment variables.
479483

tests/unit/utils/test_oauth.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_exchange_code_for_tokens_success(self, mock_post):
139139
mock_post.return_value = mock_response
140140

141141
# Mock cloud ID retrieval and token saving
142-
with patch.object(OAuthConfig, "_get_cloud_id") as mock_get_cloud_id:
142+
with patch("mcp_atlassian.utils.oauth.get_cloud_id") as mock_get_cloud_id:
143143
with patch.object(OAuthConfig, "_save_tokens") as mock_save_tokens:
144144
config = OAuthConfig(
145145
client_id="test-client-id",
@@ -157,7 +157,7 @@ def test_exchange_code_for_tokens_success(self, mock_post):
157157

158158
# Verify calls
159159
mock_post.assert_called_once()
160-
mock_get_cloud_id.assert_called_once()
160+
mock_get_cloud_id.assert_called_once_with("new-access-token")
161161
mock_save_tokens.assert_called_once()
162162

163163
@patch("requests.post")
@@ -284,42 +284,33 @@ def test_ensure_valid_token_needs_refresh_failure(self, mock_refresh):
284284

285285
@patch("requests.get")
286286
def test_get_cloud_id_success(self, mock_get):
287-
"""Test _get_cloud_id success case."""
287+
"""Test get_cloud_id success case."""
288288
# Mock response
289289
mock_response = MagicMock()
290290
mock_response.status_code = 200
291291
mock_response.json.return_value = [{"id": "test-cloud-id", "name": "Test Site"}]
292292
mock_get.return_value = mock_response
293293

294-
config = OAuthConfig(
295-
client_id="test-client-id",
296-
client_secret="test-client-secret",
297-
redirect_uri="https://example.com/callback",
298-
scope="read:jira-work write:jira-work",
299-
access_token="test-access-token",
300-
)
301-
config._get_cloud_id()
294+
from mcp_atlassian.utils.oauth import get_cloud_id
295+
296+
cloud_id = get_cloud_id("test-access-token")
302297

303298
# Check result
304-
assert config.cloud_id == "test-cloud-id"
299+
assert cloud_id == "test-cloud-id"
305300
mock_get.assert_called_once()
306301
headers = mock_get.call_args[1]["headers"]
307302
assert headers["Authorization"] == "Bearer test-access-token"
308303

309304
@patch("requests.get")
310305
def test_get_cloud_id_no_access_token(self, mock_get):
311-
"""Test _get_cloud_id with no access token."""
312-
config = OAuthConfig(
313-
client_id="test-client-id",
314-
client_secret="test-client-secret",
315-
redirect_uri="https://example.com/callback",
316-
scope="read:jira-work write:jira-work",
317-
)
318-
config._get_cloud_id()
306+
"""Test get_cloud_id with no access token."""
307+
from mcp_atlassian.utils.oauth import get_cloud_id
308+
309+
cloud_id = get_cloud_id(None)
319310

320311
# Should not make API call without token
321312
mock_get.assert_not_called()
322-
assert config.cloud_id is None
313+
assert cloud_id is None
323314

324315
def test_get_keyring_username(self):
325316
"""Test _get_keyring_username method."""

0 commit comments

Comments
 (0)