Skip to content

Commit aa06d95

Browse files
feat: add Bitbucket Pipelines OIDC detector
Reads the OIDC token from BITBUCKET_STEP_OIDC_TOKEN, which Bitbucket populates when a pipeline step sets oidc: true. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f9422d7 commit aa06d95

3 files changed

Lines changed: 83 additions & 0 deletions

File tree

cloudsmith_cli/core/credentials/oidc/detectors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
from .aws import AWSDetector
99
from .base import EnvironmentDetector
10+
from .bitbucket_pipelines import BitbucketPipelinesDetector
1011

1112
if TYPE_CHECKING:
1213
from ... import CredentialContext
1314

1415
logger = logging.getLogger(__name__)
1516

1617
_DETECTORS: list[type[EnvironmentDetector]] = [
18+
BitbucketPipelinesDetector,
1719
AWSDetector,
1820
]
1921

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Bitbucket Pipelines OIDC detector.
2+
3+
Reads an OIDC token from the ``BITBUCKET_STEP_OIDC_TOKEN`` environment variable,
4+
which Bitbucket populates when ``oidc: true`` is set on a pipeline step.
5+
6+
References:
7+
https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/
8+
https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import os
14+
15+
from .base import EnvironmentDetector
16+
17+
18+
class BitbucketPipelinesDetector(EnvironmentDetector):
19+
"""Detects Bitbucket Pipelines and reads its OIDC token from environment."""
20+
21+
name = "Bitbucket Pipelines"
22+
23+
def detect(self) -> bool:
24+
return bool(os.environ.get("BITBUCKET_STEP_OIDC_TOKEN"))
25+
26+
def get_token(self) -> str:
27+
token = os.environ.get("BITBUCKET_STEP_OIDC_TOKEN")
28+
if not token:
29+
raise ValueError(
30+
"Bitbucket Pipelines detected but BITBUCKET_STEP_OIDC_TOKEN is not "
31+
"set. Enable OIDC on the pipeline step with 'oidc: true'."
32+
)
33+
return token
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Tests for the Bitbucket Pipelines OIDC detector."""
2+
3+
from unittest import mock
4+
5+
import pytest
6+
7+
from cloudsmith_cli.core.credentials.models import CredentialContext
8+
from cloudsmith_cli.core.credentials.oidc.detectors.bitbucket_pipelines import (
9+
BitbucketPipelinesDetector,
10+
)
11+
12+
13+
@pytest.fixture
14+
def bitbucket_env():
15+
env = {
16+
"BITBUCKET_STEP_OIDC_TOKEN": "the-jwt",
17+
}
18+
with mock.patch.dict("os.environ", env, clear=True):
19+
yield env
20+
21+
22+
class TestDetect:
23+
def test_detects_when_token_present(self, bitbucket_env):
24+
detector = BitbucketPipelinesDetector(context=CredentialContext())
25+
assert detector.detect() is True
26+
27+
def test_not_detected_when_unset(self):
28+
with mock.patch.dict("os.environ", {}, clear=True):
29+
detector = BitbucketPipelinesDetector(context=CredentialContext())
30+
assert detector.detect() is False
31+
32+
def test_not_detected_when_token_empty(self, bitbucket_env):
33+
bitbucket_env["BITBUCKET_STEP_OIDC_TOKEN"] = ""
34+
with mock.patch.dict("os.environ", bitbucket_env, clear=True):
35+
detector = BitbucketPipelinesDetector(context=CredentialContext())
36+
assert detector.detect() is False
37+
38+
39+
class TestGetToken:
40+
def test_returns_token(self, bitbucket_env):
41+
detector = BitbucketPipelinesDetector(context=CredentialContext())
42+
assert detector.get_token() == "the-jwt"
43+
44+
def test_raises_when_token_missing(self):
45+
with mock.patch.dict("os.environ", {}, clear=True):
46+
detector = BitbucketPipelinesDetector(context=CredentialContext())
47+
with pytest.raises(ValueError):
48+
detector.get_token()

0 commit comments

Comments
 (0)