Azure DevOps authentication provider. Supports four auth schemes: * ``basic-pat`` — PAT with empty username, Base64-encoded as ``: `` * ``bearer`` — pre-acquired OAuth / Azure AD token * ``azure-cli`` — acquires a token via ``az account get-access-token`` * ``azure-ad`` — a
| 18 | |
| 19 | |
| 20 | class AzureDevOpsAuth(AuthProvider): |
| 21 | """Azure DevOps authentication provider. |
| 22 | |
| 23 | Supports four auth schemes: |
| 24 | |
| 25 | * ``basic-pat`` — PAT with empty username, Base64-encoded as ``:<PAT>`` |
| 26 | * ``bearer`` — pre-acquired OAuth / Azure AD token |
| 27 | * ``azure-cli`` — acquires a token via ``az account get-access-token`` |
| 28 | * ``azure-ad`` — acquires a token via OAuth2 client credentials flow |
| 29 | """ |
| 30 | |
| 31 | key = "azure-devops" |
| 32 | supported_auth_schemes = ("basic-pat", "bearer", "azure-cli", "azure-ad") |
| 33 | |
| 34 | def auth_headers(self, token: str, auth_scheme: str) -> dict[str, str]: |
| 35 | """Build the ``Authorization`` header for the given scheme.""" |
| 36 | if auth_scheme == "basic-pat": |
| 37 | encoded = base64.b64encode(f":{token}".encode("ascii")).decode("ascii") |
| 38 | return {"Authorization": f"Basic {encoded}"} |
| 39 | if auth_scheme in ("bearer", "azure-cli", "azure-ad"): |
| 40 | return {"Authorization": f"Bearer {token}"} |
| 41 | raise ValueError( |
| 42 | f"AzureDevOpsAuth does not support auth scheme {auth_scheme!r}" |
| 43 | ) |
| 44 | |
| 45 | def resolve_token(self, entry: AuthConfigEntry) -> str | None: |
| 46 | """Resolve token, with special handling for azure-cli and azure-ad.""" |
| 47 | if entry.auth == "azure-cli": |
| 48 | return self._acquire_via_az_cli() |
| 49 | if entry.auth == "azure-ad": |
| 50 | return self._acquire_via_client_credentials(entry) |
| 51 | return super().resolve_token(entry) |
| 52 | |
| 53 | # -- Token acquisition ------------------------------------------------ |
| 54 | |
| 55 | @staticmethod |
| 56 | def _acquire_via_az_cli() -> str | None: |
| 57 | """Run ``az account get-access-token`` and return the access token.""" |
| 58 | try: |
| 59 | result = subprocess.run( # noqa: S603, S607 |
| 60 | [ |
| 61 | "az", |
| 62 | "account", |
| 63 | "get-access-token", |
| 64 | "--resource", |
| 65 | _ADO_RESOURCE_ID, |
| 66 | "--output", |
| 67 | "json", |
| 68 | ], |
| 69 | capture_output=True, |
| 70 | text=True, |
| 71 | timeout=30, |
| 72 | check=False, |
| 73 | ) |
| 74 | if result.returncode != 0: |
| 75 | return None |
| 76 | payload = _json.loads(result.stdout) |
| 77 | token = payload.get("accessToken", "").strip() |
no outgoing calls