def test_persistent_cache_multiple_clients(): """the credential shouldn't use tokens issued to other service principals""" access_token_a = "token a" access_token_b = "not " + access_token_a transport_a = msal_validating_transport( requests=[Request()], responses=[mock_response(json_payload=build_aad_response(access_token=access_token_a))] ) transport_b = msal_validating_transport( requests=[Request()], responses=[mock_response(json_payload=build_aad_response(access_token=access_token_b))] ) cache = TokenCache() with patch("azure.identity._internal.persistent_cache._load_persistent_cache") as mock_cache_loader: mock_cache_loader.return_value = Mock(wraps=cache) credential_a = ClientSecretCredential( "tenant-id", "client-a", "...", _enable_persistent_cache=True, transport=transport_a ) assert mock_cache_loader.call_count == 1, "credential should load the persistent cache" credential_b = ClientSecretCredential( "tenant-id", "client-b", "...", _enable_persistent_cache=True, transport=transport_b ) assert mock_cache_loader.call_count == 2, "credential should load the persistent cache" # A caches a token scope = "scope" token_a = credential_a.get_token(scope) assert token_a.token == access_token_a assert transport_a.send.call_count == 3 # two MSAL discovery requests, one token request # B should get a different token for the same scope token_b = credential_b.get_token(scope) assert token_b.token == access_token_b assert transport_b.send.call_count == 3
class MemoryCacheRead(PerfStressTest): def __init__(self, arguments): super().__init__(arguments) client_id = self.get_from_env("AZURE_CLIENT_ID") tenant_id = self.get_from_env("AZURE_TENANT_ID") secret = self.get_from_env("AZURE_CLIENT_SECRET") self.credential = ClientSecretCredential(tenant_id, client_id, secret) self.async_credential = AsyncClientSecretCredential( tenant_id, client_id, secret) self.scope = "https://vault.azure.net/.default" async def global_setup(self): """Cache an access token""" await super().global_setup() self.credential.get_token(self.scope) await self.async_credential.get_token(self.scope) def run_sync(self): self.credential.get_token(self.scope) async def run_async(self): await self.async_credential.get_token(self.scope) async def close(self): await self.async_credential.close() await super().close()
def test_multitenant_authentication_not_allowed(): expected_tenant = "expected-tenant" expected_token = "***" def send(request, **_): parsed = urlparse(request.url) if "/oauth2/v2.0/token" not in parsed.path: return get_discovery_response("https://{}/{}".format( parsed.netloc, expected_tenant)) tenant = parsed.path.split("/")[1] token = expected_token if tenant == expected_tenant else expected_token * 2 return mock_response(json_payload=build_aad_response( access_token=token)) credential = ClientSecretCredential(expected_tenant, "client-id", "secret", transport=Mock(send=send)) token = credential.get_token("scope") assert token.token == expected_token token = credential.get_token("scope", tenant_id=expected_tenant) assert token.token == expected_token with patch.dict( "os.environ", {EnvironmentVariables.AZURE_IDENTITY_DISABLE_MULTITENANTAUTH: "true"}): token = credential.get_token("scope", tenant_id="un" + expected_tenant) assert token.token == expected_token
def test_regional_authority(): """the credential should configure MSAL with a regional authority specified via kwarg or environment variable""" mock_confidential_client = Mock(return_value=Mock( acquire_token_silent_with_error=lambda *_, **__: { "access_token": "**", "expires_in": 3600 })) for region in RegionalAuthority: mock_confidential_client.reset_mock() # region can be configured via environment variable with patch.dict( "os.environ", {EnvironmentVariables.AZURE_REGIONAL_AUTHORITY_NAME: region}, clear=True): credential = ClientSecretCredential("tenant", "client-id", "secret") with patch("msal.ConfidentialClientApplication", mock_confidential_client): credential.get_token("scope") assert mock_confidential_client.call_count == 1 _, kwargs = mock_confidential_client.call_args assert kwargs["azure_region"] == region
def test_authority(authority): """the credential should accept an authority, with or without scheme, as an argument or environment variable""" tenant_id = "expected-tenant" parsed_authority = urlparse(authority) expected_netloc = parsed_authority.netloc or authority expected_authority = "https://{}/{}".format(expected_netloc, tenant_id) mock_ctor = Mock( return_value=Mock(acquire_token_silent_with_error=lambda *_, **__: {"access_token": "**", "expires_in": 42}) ) credential = ClientSecretCredential(tenant_id, "client-id", "secret", authority=authority) with patch("msal.ConfidentialClientApplication", mock_ctor): # must call get_token because the credential constructs the MSAL application lazily credential.get_token("scope") assert mock_ctor.call_count == 1 _, kwargs = mock_ctor.call_args assert kwargs["authority"] == expected_authority mock_ctor.reset_mock() # authority can be configured via environment variable with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True): credential = ClientSecretCredential(tenant_id, "client-id", "secret") with patch("msal.ConfidentialClientApplication", mock_ctor): credential.get_token("scope") assert mock_ctor.call_count == 1 _, kwargs = mock_ctor.call_args assert kwargs["authority"] == expected_authority
def test_client_secret_credential_cache(): expired = "this token's expired" now = time.time() expired_on = int(now - 300) expired_token = AccessToken(expired, expired_on) token_payload = { "access_token": expired, "expires_in": 0, "ext_expires_in": 0, "expires_on": expired_on, "not_before": now, "token_type": "Bearer", "resource": str(uuid.uuid1()), } mock_response = Mock( text=lambda: json.dumps(token_payload), headers={"content-type": "application/json"}, status_code=200, content_type=["application/json"], ) mock_send = Mock(return_value=mock_response) credential = ClientSecretCredential("client_id", "secret", tenant_id=str(uuid.uuid1()), transport=Mock(send=mock_send)) scopes = ("https://foo.bar/.default", "https://bar.qux/.default") token = credential.get_token(*scopes) assert token == expired_token token = credential.get_token(*scopes) assert token == expired_token assert mock_send.call_count == 2
def test_client_secret_credential_cache(): expired = "this token's expired" now = int(time.time()) expired_on = now - 3600 expired_token = AccessToken(expired, expired_on) token_payload = { "access_token": expired, "expires_in": 0, "ext_expires_in": 0, "expires_on": expired_on, "not_before": now, "token_type": "Bearer", } mock_send = Mock(return_value=mock_response(json_payload=token_payload)) scope = "scope" credential = ClientSecretCredential("client_id", "secret", tenant_id="some-guid", transport=Mock(send=mock_send)) # get_token initially returns the expired token because the credential # doesn't check whether tokens it receives from the service have expired token = credential.get_token(scope) assert token == expired_token access_token = "new token" token_payload["access_token"] = access_token token_payload["expires_on"] = now + 3600 valid_token = AccessToken(access_token, now + 3600) # second call should observe the cached token has expired, and request another token = credential.get_token(scope) assert token == valid_token assert mock_send.call_count == 2
def test_no_scopes(): """The credential should raise ValueError when get_token is called with no scopes""" credential = ClientSecretCredential("tenant-id", "client-id", "client-secret") with pytest.raises(ValueError): credential.get_token()
def test_request_url(authority): """the credential should accept an authority, with or without scheme, as an argument or environment variable""" tenant_id = "expected_tenant" access_token = "***" parsed_authority = urlparse(authority) expected_netloc = parsed_authority.netloc or authority # "localhost" parses to netloc "", path "localhost" def mock_send(request, **kwargs): actual = urlparse(request.url) assert actual.scheme == "https" assert actual.netloc == expected_netloc assert actual.path.startswith("/" + tenant_id) return mock_response(json_payload={"token_type": "Bearer", "expires_in": 42, "access_token": access_token}) credential = ClientSecretCredential( tenant_id, "client-id", "secret", transport=Mock(send=mock_send), authority=authority ) token = credential.get_token("scope") assert token.token == access_token # authority can be configured via environment variable with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True): credential = ClientSecretCredential(tenant_id, "client-id", "secret", transport=Mock(send=mock_send)) credential.get_token("scope") assert token.token == access_token
def test_user_agent(): transport = msal_validating_transport( requests=[Request(required_headers={"User-Agent": USER_AGENT})], responses=[mock_response(json_payload=build_aad_response(access_token="**"))], ) credential = ClientSecretCredential("tenant-id", "client-id", "client-secret", transport=transport) credential.get_token("scope")
def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) def send(*_, **__): return mock_response(json_payload=build_aad_response(access_token="**")) credential = ClientSecretCredential( "tenant-id", "client-id", "client-secret", policies=[ContentDecodePolicy(), policy], transport=Mock(send=send) ) credential.get_token("scope") assert policy.on_request.called
def test_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) transport = msal_validating_transport( requests=[Request()], responses=[mock_response(json_payload=build_aad_response(access_token="**"))], ) credential = ClientSecretCredential( "tenant-id", "client-id", "client-secret", policies=[ContentDecodePolicy(), policy], transport=transport ) credential.get_token("scope") assert policy.on_request.called
def getAccessToken(config): print("Get AAD access token") aadTokenFile = '/home/ubuntu/aad_oauth_token.json' if os.name == 'nt': aadTokenFile = '__pycache__\\aad_oauth_token.json' if path.exists(aadTokenFile): f = open(aadTokenFile, ) data = json.load(f) expireOn = data[1] if int(time.time()) < expireOn: print('Reuse existing token') print("Access token lifetime: " + str(data[1] - int(time.time()))) return data[0] print("Get token from AzureAD") client = ClientSecretCredential(config["TenantId"], config["ClientId"], config["ClientSecret"], authority=config["Authority"]) accessToken = client.get_token(config["Scopes"]) print("Access token lifetime: " + str(accessToken[1] - int(time.time()))) pathSaveFile = '/home/ubuntu/aad_oauth_token.json' if os.name == 'nt': pathSaveFile = '__pycache__\\aad_oauth_token.json' with open(pathSaveFile, 'w') as outfile: json.dump(accessToken, outfile) return accessToken.token
def test_live_multitenant_authentication(live_service_principal): # first create a credential with a non-existent tenant credential = ClientSecretCredential( "...", live_service_principal["client_id"], live_service_principal["client_secret"]) # then get a valid token for an actual tenant token = credential.get_token("https://vault.azure.net/.default", tenant_id=live_service_principal["tenant_id"]) assert token.token assert token.expires_on
def test_client_secret_credential(live_identity_settings): credential = ClientSecretCredential( live_identity_settings["client_id"], live_identity_settings["client_secret"], live_identity_settings["tenant_id"], ) token = credential.get_token(ARM_SCOPE) assert token assert token.token assert token.expires_on
def test_multitenant_authentication_not_allowed(): """get_token(tenant_id=...) should raise when allow_multitenant_authentication is False (the default)""" expected_tenant = "expected-tenant" expected_token = "***" def send(request, **_): parsed = urlparse(request.url) if "/oauth2/v2.0/token" not in parsed.path: return get_discovery_response("https://{}/{}".format( parsed.netloc, expected_tenant)) tenant = parsed.path.split("/")[1] token = expected_token if tenant == expected_tenant else expected_token * 2 return mock_response(json_payload=build_aad_response( access_token=token)) credential = ClientSecretCredential(expected_tenant, "client-id", "secret", transport=Mock(send=send)) token = credential.get_token("scope") assert token.token == expected_token # explicitly specifying the configured tenant is okay token = credential.get_token("scope", tenant_id=expected_tenant) assert token.token == expected_token # but any other tenant should get an error with pytest.raises(ClientAuthenticationError, match="allow_multitenant_authentication"): credential.get_token("scope", tenant_id="un" + expected_tenant) # ...unless the compat switch is enabled with patch.dict("os.environ", { EnvironmentVariables.AZURE_IDENTITY_ENABLE_LEGACY_TENANT_SELECTION: "true" }): token = credential.get_token("scope", tenant_id="un" + expected_tenant) assert token.token == expected_token, "credential should ignore tenant_id kwarg when the compat switch is enabled"
def init_access_token(self, ad_tenant_id: str = None, sp_client_id: str = None, ad_domain: str = None, debug_mode: bool = False) -> str: """ Initialize access token to access ygdra api. Parameters ---------- ad_tenant_id : str Tenant id. If None (default), will parse AD_TENANT_ID environment variable. sp_client_id: str Service principal client id. If None (default), will parse SP_CLIENT_ID environment variable. ad_domain: str Principal domain. If None (default), will parse AD_DOMAIN environment variable. debug_mode: bool Indicates if you want output intermediate steps. False by default. """ if not ad_tenant_id: ad_tenant_id = os.getenv('AD_TENANT_ID') if not sp_client_id: sp_client_id = os.getenv('SP_CLIENT_ID') if not ad_domain: ad_domain = os.getenv('AD_DOMAIN') if not ad_tenant_id: raise ValueError('ad_tenant_id input value is null or empty') if not sp_client_id: raise ValueError('sp_client_id input value is null or empty') if not ad_domain: raise ValueError('ad_domain input value is null or empty') credential = ClientSecretCredential(ad_tenant_id, sp_client_id, self.sp_client_secret) scope = f'https://{ad_domain}/{sp_client_id}/.default' if (debug_mode): print(f'tenantId: {ad_tenant_id}') print(f'clientId: {sp_client_id}') print(f'domain: {ad_domain}') print(f'scope: {scope}') access_token = credential.get_token(scope) self.token = access_token.token return access_token.token
def test_allow_multitenant_authentication(): """When allow_multitenant_authentication is True, the credential should respect get_token(tenant_id=...)""" first_tenant = "first-tenant" first_token = "***" second_tenant = "second-tenant" second_token = first_token * 2 def send(request, **_): parsed = urlparse(request.url) tenant = parsed.path.split("/")[1] assert tenant in (first_tenant, second_tenant, "common"), 'unexpected tenant "{}"'.format(tenant) if "/oauth2/v2.0/token" not in parsed.path: return get_discovery_response("https://{}/{}".format( parsed.netloc, tenant)) token = first_token if tenant == first_tenant else second_token return mock_response(json_payload=build_aad_response( access_token=token)) credential = ClientSecretCredential(first_tenant, "client-id", "secret", allow_multitenant_authentication=True, transport=Mock(send=send)) token = credential.get_token("scope") assert token.token == first_token token = credential.get_token("scope", tenant_id=first_tenant) assert token.token == first_token token = credential.get_token("scope", tenant_id=second_tenant) assert token.token == second_token # should still default to the first tenant token = credential.get_token("scope") assert token.token == first_token
class PersistentCacheRead(PerfStressTest): def __init__(self, arguments): super().__init__(arguments) client_id = self.get_from_env("AZURE_CLIENT_ID") tenant_id = self.get_from_env("AZURE_TENANT_ID") secret = self.get_from_env("AZURE_CLIENT_SECRET") cache_options = TokenCachePersistenceOptions( allow_unencrypted_storage=True) self.credential = ClientSecretCredential( tenant_id, client_id, secret, cache_persistence_options=cache_options) self.async_credential = AsyncClientSecretCredential( tenant_id, client_id, secret, cache_persistence_options=cache_options) self.scope = "https://vault.azure.net/.default" async def global_setup(self): """Cache an access token""" await super().global_setup() self.credential.get_token(self.scope) await self.async_credential.get_token(self.scope) def run_sync(self): self.credential.get_token(self.scope) async def run_async(self): await self.async_credential.get_token(self.scope) async def close(self): await self.async_credential.close() await super().close()
def test_request_url(): authority = "localhost" tenant_id = "expected_tenant" access_token = "***" def mock_send(request, **kwargs): parsed = urlparse(request.url) assert parsed.scheme == "https" assert parsed.netloc == authority assert parsed.path.startswith("/" + tenant_id) return mock_response(json_payload={"token_type": "Bearer", "expires_in": 42, "access_token": access_token}) credential = ClientSecretCredential( tenant_id, "client-id", "secret", transport=Mock(send=mock_send), authority=authority ) token = credential.get_token("scope") assert token.token == access_token # authority can be configured via environment variable with patch.dict("os.environ", {EnvironmentVariables.AZURE_AUTHORITY_HOST: authority}, clear=True): credential = ClientSecretCredential(tenant_id, "client-id", "secret", transport=Mock(send=mock_send)) credential.get_token("scope") assert token.token == access_token
def getAccessToken(self, config): print("Get AAD access token") if (self.in_memory_token != None and int(time.time()) < self.in_memory_token[1]): print("Return token from memory") print("Access token lifetime: " + str(self.in_memory_token[1] - int(time.time()))) return self.in_memory_token[0] aadTokenFile = '/home/ubuntu/aad_oauth_token.json' if os.name == 'nt': aadTokenFile = '__pycache__\\aad_oauth_token.json' if path.exists(aadTokenFile): f = open(aadTokenFile, ) self.in_memory_token = json.load(f) expireOn = self.in_memory_token[1] if int(time.time()) < expireOn: print('Reuse existing token') print("Access token lifetime: " + str(self.in_memory_token[1] - int(time.time()))) return self.in_memory_token[0] print("Get token from AzureAD") client = ClientSecretCredential(config["TenantId"], config["ClientId"], config["ClientSecret"], authority=config["Authority"]) accessToken = client.get_token(config["Scopes"]) print("Access token lifetime: " + str(accessToken[1] - int(time.time()))) try: pathSaveFile = '/home/ubuntu/aad_oauth_token.json' if os.name == 'nt': pathSaveFile = '__pycache__\\aad_oauth_token.json' with open(pathSaveFile, 'w') as outfile: json.dump(accessToken, outfile) except: print("Error saving token to file") self.in_memory_token = accessToken return accessToken.token
def get_access_token(tenant_id, client_id, client_secret, scope): credentials = ClientSecretCredential(tenant_id=tenant_id, client_id=client_id, client_secret=client_secret) return credentials.get_token(scope)
from sys import path path.append('\\Program Files\\Microsoft.NET\\ADOMD.NET\\150') from pyadomd import Pyadomd from azure.identity import ClientSecretCredential scope = 'https://northeurope.asazure.windows.net/.default' tenant_id = input('Tenant id: ') client_id = input('Client id: ') client_secret = input('Client secret: ') authority = f'https://login.microsoftonline.com/' credential = ClientSecretCredential(tenant_id, client_id, client_secret, authority=authority) token = credential.get_token(scope) serverName = 'asazure://northeurope.asazure.windows.net/aast' connectionString = f'Provider=MSOLAP;Data Source={serverName};Initial Catalog=Test;User ID=;Password={token.token};Persist Security Info=True;Impersonation Level=Impersonate;' query = "EVALUATE SUMMARIZECOLUMNS('Sample Data'[Value1])" with Pyadomd(connectionString) as conn: with conn.cursor().execute(query) as cur: print(cur.fetchall())