diff --git a/sk/azure.py b/sk/azure.py index b57ee43..1d57ac5 100644 --- a/sk/azure.py +++ b/sk/azure.py @@ -12,8 +12,53 @@ from cryptography import x509 import hashlib import base64 +DEVOPS_SCOPE = "https://app.vssps.visualstudio.com/.default" + +def get_token( + tenant_id: str | None = None, + client_id: str | None = None, + client_secret: str | None = None, + pem_path: str | None = None + ) -> str: + """ + Obtain a token for DevOps using DefaultAzureCredential. + """ + try: + if tenant_id and client_id and client_secret: + from azure.identity import ClientSecretCredential + return ClientSecretCredential( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret + ).get_token(DEVOPS_SCOPE).token + elif tenant_id and client_id and pem_path: + from azure.identity import CertificateCredential + return CertificateCredential( + tenant_id=tenant_id, + client_id=client_id, + certificate_path=pem_path + ).get_token(DEVOPS_SCOPE).token + else: + from azure.identity import DefaultAzureCredential + return DefaultAzureCredential().get_token(DEVOPS_SCOPE).token + except ImportError: + if tenant_id and client_id and client_secret: + return secret_credentials_auth( + tenant_id=tenant_id, + client_id=client_id, + client_secret=client_secret + ) + elif tenant_id and client_id and pem_path: + return certificate_credentials_auth( + tenant_id=tenant_id, + client_id=client_id, + pem_path=pem_path + ) + else: + raise ValueError("Either client_secret or pem_path must be provided, if no azure-identity package is installed.") + def secret_credentials_auth( - scope: str = "https://app.vssps.visualstudio.com/.default", + scope: str = DEVOPS_SCOPE, tenant_id: str = os.environ.get("AZURE_TENANT_ID", ""), client_id: str = os.environ.get("AZURE_CLIENT_ID", ""), client_secret: str = os.environ.get("AZURE_CLIENT_SECRET") @@ -33,7 +78,7 @@ def secret_credentials_auth( return r.json().get("access_token", "") def certificate_credentials_auth( - scope: str = "https://app.vssps.visualstudio.com/.default", + scope: str = DEVOPS_SCOPE, tenant_id: str = os.environ.get("AZURE_TENANT_ID", ""), client_id: str = os.environ.get("AZURE_CLIENT_ID", ""), pem_path: str = os.environ.get("AZURE_CLIENT_CERTIFICATE_PATH", "") diff --git a/sk/devops.py b/sk/devops.py index 335969a..8c3a412 100644 --- a/sk/devops.py +++ b/sk/devops.py @@ -108,8 +108,6 @@ class DevOps(): class Organization(DevOps): def __init__(self, org_url: str, token: str | None = None, api_version: str = DEVOPS_API_VERSION): - if token is None: - token = DefaultAzureCredential().get_token(DEVOPS_SCOPE).token super().__init__(org_url, token, api_version) @property diff --git a/tests.py b/tests.py index 30f83a1..ae89bad 100755 --- a/tests.py +++ b/tests.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 import unittest import requests -from azure.identity import DefaultAzureCredential -from sk.devops import DEVOPS_SCOPE, Organization, Repository, Project, Item +from sk.devops import Organization, Repository, Project, Item +from sk.azure import get_token # Get the token outside the test class to speed up tests. # Each Unit test instantinates the class, so doing it here avoids repeated authentication. -token = DefaultAzureCredential().get_token(DEVOPS_SCOPE).token +token = get_token() class Test01(unittest.TestCase): def setUp(self):