def test_not_logged_in(): """When the CLI isn't logged in, the credential should raise an error containing the CLI's output""" output = "ERROR: Please run 'az login' to setup account." with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output)): with pytest.raises(ClientAuthenticationError, match=output): AzureCliCredential().get_token("scope")
def test_get_token(): """The credential should parse the CLI's output to an AccessToken""" access_token = "access token" valid_seconds = 42 successful_output = json.dumps({ # expiresOn is a naive datetime representing valid_seconds from the epoch "expiresOn": datetime.fromtimestamp(valid_seconds).strftime("%Y-%m-%d %H:%M:%S.%f"), "accessToken": access_token, "subscription": "some-guid", "tenant": "some-guid", "tokenType": "Bearer", }) with mock.patch(CHECK_OUTPUT, mock.Mock(return_value=successful_output)): token = AzureCliCredential().get_token("scope") assert token.token == access_token assert type(token.expires_on) == int assert token.expires_on == valid_seconds
def test_multitenant_authentication(): default_tenant = "first-tenant" first_token = "***" second_tenant = "second-tenant" second_token = first_token * 2 def fake_check_output(command_line, **_): match = re.search("--tenant (.*)", command_line[-1]) tenant = match.groups()[0] if match else default_tenant assert tenant in (default_tenant, second_tenant), 'unexpected tenant "{}"'.format(tenant) return json.dumps( { "expiresOn": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), "accessToken": first_token if tenant == default_tenant else second_token, "subscription": "some-guid", "tenant": tenant, "tokenType": "Bearer", } ) credential = AzureCliCredential() with mock.patch(CHECK_OUTPUT, fake_check_output): token = credential.get_token("scope") assert token.token == first_token token = credential.get_token("scope", tenant_id=default_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
def test_cli_not_installed_linux(): """The credential should raise CredentialUnavailableError when the CLI isn't installed""" output = "/bin/sh: 1: az: not found" with mock.patch(CHECK_OUTPUT, raise_called_process_error(127, output)): with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND): AzureCliCredential().get_token("scope")
def test_unexpected_error(): """When the CLI returns an unexpected error, the credential should raise an error containing the CLI's output""" output = "something went wrong" with mock.patch(CHECK_OUTPUT, raise_called_process_error(42, output)): with pytest.raises(ClientAuthenticationError, match=output): AzureCliCredential().get_token("scope")
def test_caching(): scope = "scope" now = int(time.time()) expected_expires_on = now + 3600 expected_token = "*" transport = validating_transport( requests=[Request(url="http://localhost")], responses=[ mock_response( json_payload={ "access_token": expected_token, "expires_in": 3600, "expires_on": expected_expires_on, "resource": scope, "token_type": "Bearer", } ) ], ) client = ManagedIdentityClient( request_factory=lambda _, __: HttpRequest("GET", "http://localhost"), transport=transport ) token = client.get_cached_token(scope) assert not token with mock.patch(ManagedIdentityClient.__module__ + ".time.time", lambda: now): token = client.request_token(scope) assert token.expires_on == expected_expires_on assert token.token == expected_token token = client.get_cached_token(scope) assert token.expires_on == expected_expires_on assert token.token == expected_token
def test_cli_not_installed_windows(): """The credential should raise CredentialUnavailableError when the CLI isn't installed""" output = "'az' is not recognized as an internal or external command, operable program or batch file." with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output)): with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND): AzureCliCredential().get_token("scope")
def test_multitenant_authentication_not_allowed(): expected_tenant = "expected-tenant" expected_token = "***" def fake_check_output(command_line, **_): match = re.search("--tenant (.*)", command_line[-1]) assert match is None or match[1] == expected_tenant return json.dumps( { "expiresOn": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), "accessToken": expected_token, "subscription": "some-guid", "tenant": expected_token, "tokenType": "Bearer", } ) credential = AzureCliCredential() with mock.patch(CHECK_OUTPUT, fake_check_output): token = credential.get_token("scope") assert token.token == expected_token with mock.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_get_token(): """The credential should parse the CLI's output to an AccessToken""" access_token = "access token" expected_expires_on = 1602015811 successful_output = json.dumps({ "expiresOn": datetime.fromtimestamp(expected_expires_on).strftime( "%Y-%m-%d %H:%M:%S.%f"), "accessToken": access_token, "subscription": "some-guid", "tenant": "some-guid", "tokenType": "Bearer", }) with mock.patch(CHECK_OUTPUT, mock.Mock(return_value=successful_output)): token = AzureCliCredential().get_token("scope") assert token.token == access_token assert type(token.expires_on) == int assert token.expires_on == expected_expires_on
def test_not_logged_in(): """When the CLI isn't logged in, the credential should raise CredentialUnavailableError""" output = "ERROR: Please run 'az login' to setup account." with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output)): with pytest.raises(CredentialUnavailableError, match=NOT_LOGGED_IN): AzureCliCredential().get_token("scope")
def test_timeout(): """The credential should raise CredentialUnavailableError when the subprocess times out""" from subprocess import TimeoutExpired with mock.patch(CHECK_OUTPUT, mock.Mock(side_effect=TimeoutExpired("", 42))): with pytest.raises(CredentialUnavailableError): AzureCliCredential().get_token("scope")
def test_multiple_scopes(): """The credential should raise ValueError when get_token is called with more than one scope""" with mock.patch("os.environ", {EnvironmentVariables.MSI_ENDPOINT: "https://url"}): credential = MsiCredential() with pytest.raises(ValueError): credential.get_token("one scope", "and another")
def test_no_scopes(): """The credential should raise ValueError when get_token is called with no scopes""" with mock.patch("os.environ", {EnvironmentVariables.MSI_ENDPOINT: "https://url"}): credential = MsiCredential() with pytest.raises(ValueError): credential.get_token()
def test_parsing_error_does_not_expose_token(output): """Errors during CLI output parsing shouldn't expose access tokens in that output""" with mock.patch(CHECK_OUTPUT, mock.Mock(return_value=output)): with pytest.raises(ClientAuthenticationError) as ex: AzureCliCredential().get_token("scope") assert "secret value" not in str(ex.value) assert "secret value" not in repr(ex.value)
def test_subprocess_error_does_not_expose_token(output): """Errors from the subprocess shouldn't expose access tokens in CLI output""" with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output=output)): with pytest.raises(ClientAuthenticationError) as ex: AzureCliCredential().get_token("scope") assert "secret value" not in str(ex.value) assert "secret value" not in repr(ex.value)
def test_username_password_configuration(): """the credential should pass expected values and any keyword arguments to its inner credential""" client_id = "client-id" username = "******" password = "******" bar = "bar" environment = { EnvironmentVariables.AZURE_CLIENT_ID: client_id, EnvironmentVariables.AZURE_USERNAME: username, EnvironmentVariables.AZURE_PASSWORD: password, } with mock.patch(EnvironmentCredential.__module__ + ".UsernamePasswordCredential") as mock_credential: with mock.patch.dict("os.environ", environment, clear=True): EnvironmentCredential(foo=bar) assert mock_credential.call_count == 1 _, kwargs = mock_credential.call_args assert kwargs["client_id"] == client_id assert kwargs["username"] == username assert kwargs["password"] == password assert kwargs["foo"] == bar # optional tenant id should be used when set tenant_id = "tenant-id" environment = dict(environment, **{EnvironmentVariables.AZURE_TENANT_ID: tenant_id}) with mock.patch(EnvironmentCredential.__module__ + ".UsernamePasswordCredential") as mock_credential: with mock.patch.dict("os.environ", environment, clear=True): EnvironmentCredential(foo=bar) assert mock_credential.call_count == 1 _, kwargs = mock_credential.call_args assert kwargs["client_id"] == client_id assert kwargs["username"] == username assert kwargs["password"] == password assert kwargs["tenant_id"] == tenant_id assert kwargs["foo"] == bar
def test_passes_authority_argument(credential_name, environment_variables): """the credential pass the 'authority' keyword argument to its inner credential""" authority = "authority" with mock.patch.dict("os.environ", {variable: "foo" for variable in environment_variables}, clear=True): with mock.patch(EnvironmentCredential.__module__ + "." + credential_name) as mock_credential: EnvironmentCredential(authority=authority) assert mock_credential.call_count == 1 _, kwargs = mock_credential.call_args assert kwargs["authority"] == authority
def test_token_cache_persistence_options(): with mock.patch("azure.identity._persistent_cache.msal_extensions"): # [START snippet] cache_options = TokenCachePersistenceOptions() credential = InteractiveBrowserCredential( cache_persistence_options=cache_options) # specify a cache name to isolate the cache from other applications TokenCachePersistenceOptions(name="my_application") # configure the cache to fall back to unencrypted storage when encryption isn't available TokenCachePersistenceOptions(allow_unencrypted_storage=True)
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 fake_check_output(command_line, **_): match = re.search("--tenant (.*)", command_line[-1]) assert match is None or match[1] == expected_tenant return json.dumps({ "expiresOn": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), "accessToken": expected_token, "subscription": "some-guid", "tenant": expected_token, "tokenType": "Bearer", }) credential = AzureCliCredential() with mock.patch(CHECK_OUTPUT, fake_check_output): token = credential.get_token("scope") assert token.token == expected_token # specifying a 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 mock.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 test_certificate_configuration(): """the credential should pass expected values and any keyword arguments to its inner credential""" client_id = "client-id" certificate_path = "..." tenant_id = "tenant_id" bar = "bar" environment = { EnvironmentVariables.AZURE_CLIENT_ID: client_id, EnvironmentVariables.AZURE_CLIENT_CERTIFICATE_PATH: certificate_path, EnvironmentVariables.AZURE_TENANT_ID: tenant_id, } with mock.patch(EnvironmentCredential.__module__ + ".CertificateCredential") as mock_credential: with mock.patch.dict("os.environ", environment, clear=True): EnvironmentCredential(foo=bar) assert mock_credential.call_count == 1 _, kwargs = mock_credential.call_args assert kwargs["client_id"] == client_id assert kwargs["certificate_path"] == certificate_path assert kwargs["tenant_id"] == tenant_id assert kwargs["foo"] == bar
def test_cannot_execute_shell(): """The credential should raise CredentialUnavailableError when the subprocess doesn't start""" with mock.patch(CHECK_OUTPUT, mock.Mock(side_effect=OSError())): with pytest.raises(CredentialUnavailableError): AzureCliCredential().get_token("scope")