Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/identity/azure-identity/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Unreleased

- All credential pipelines include `ProxyPolicy`
([#8945](https://github.com/Azure/azure-sdk-for-python/pull/8945))


# 2019-11-27 1.1.0

- Constructing `DefaultAzureCredential` no longer raises `ImportError` on Python
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def __init__(
config = config or self._create_config(**kwargs)
policies = policies or [
ContentDecodePolicy(),
config.proxy_policy,
config.retry_policy,
config.logging_policy,
DistributedTracingPolicy(**kwargs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def _build_pipeline(self, config=None, policies=None, transport=None, **kwargs):
config = config or self._create_config(**kwargs)
policies = policies or [
ContentDecodePolicy(),
config.proxy_policy,
config.retry_policy,
config.logging_policy,
DistributedTracingPolicy(**kwargs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(
config = config or self._create_config(**kwargs)
policies = policies or [
ContentDecodePolicy(),
config.proxy_policy,
config.retry_policy,
config.logging_policy,
DistributedTracingPolicy(**kwargs),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
DistributedTracingPolicy,
HttpLoggingPolicy,
NetworkTraceLoggingPolicy,
ProxyPolicy,
)
from azure.core.pipeline.transport import AioHttpTransport, HttpRequest

Expand All @@ -40,6 +41,7 @@ def __init__(

config = config or self._create_config(**kwargs)
policies = policies or [
config.proxy_policy,
config.retry_policy,
config.logging_policy,
DistributedTracingPolicy(**kwargs),
Expand Down Expand Up @@ -113,6 +115,7 @@ def post(
@staticmethod
def _create_config(**kwargs: "Any") -> Configuration:
config = Configuration(**kwargs)
config.proxy_policy = ProxyPolicy(**kwargs)
config.logging_policy = NetworkTraceLoggingPolicy(**kwargs)
config.retry_policy = AsyncRetryPolicy(**kwargs)
return config
9 changes: 8 additions & 1 deletion sdk/identity/azure-identity/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
# ------------------------------------
import base64
import json
import six
import time

from azure.core.pipeline.policies import SansIOHTTPPolicy
import six

try:
from unittest import mock
except ImportError: # python < 3.3
Expand Down Expand Up @@ -104,6 +106,11 @@ def mock_response(status_code=200, headers={}, json_payload=None):
return response


def get_discovery_response(endpoint="https://a/b"):
aad_metadata_endpoint_names = ("authorization_endpoint", "token_endpoint", "tenant_discovery_endpoint")
return mock_response(json_payload={name: endpoint for name in aad_metadata_endpoint_names})


def validating_transport(requests, responses):
if len(requests) != len(responses):
raise ValueError("each request must have one response")
Expand Down
18 changes: 18 additions & 0 deletions sdk/identity/azure-identity/tests/test_auth_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,32 @@
# Licensed under the MIT License.
# ------------------------------------
from azure.core.credentials import AccessToken
from azure.core.pipeline.policies import SansIOHTTPPolicy
from azure.identity import AuthorizationCodeCredential

from helpers import build_aad_response, mock_response

try:
from unittest.mock import Mock
except ImportError: # python < 3.3
from mock import Mock # type: ignore


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 = AuthorizationCodeCredential(
"tenant-id", "client-id", "auth-code", "http://localhost", policies=[policy], transport=Mock(send=send)
)

credential.get_token("scope")

assert policy.on_request.called


def test_auth_code_credential():
client_id = "client id"
tenant_id = "tenant"
Expand Down
19 changes: 19 additions & 0 deletions sdk/identity/azure-identity/tests/test_auth_code_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,28 @@

from azure.core.credentials import AccessToken
from azure.core.exceptions import ClientAuthenticationError
from azure.core.pipeline.policies import SansIOHTTPPolicy
from azure.identity.aio import AuthorizationCodeCredential
import pytest

from helpers import build_aad_response, mock_response


@pytest.mark.asyncio
async def test_policies_configurable():
policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock())

async def send(*_, **__):
return mock_response(json_payload=build_aad_response(access_token="**"))

credential = AuthorizationCodeCredential(
"tenant-id", "client-id", "auth-code", "http://localhost", policies=[policy], transport=Mock(send=send)
)

await credential.get_token("scope")

assert policy.on_request.called


@pytest.mark.asyncio
async def test_auth_code_credential():
Expand Down
18 changes: 17 additions & 1 deletion sdk/identity/azure-identity/tests/test_certificate_credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import json
import os

from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
from azure.identity import CertificateCredential
from six.moves.urllib_parse import urlparse
from helpers import urlsafeb64_decode, mock_response
from helpers import build_aad_response, urlsafeb64_decode, mock_response

try:
from unittest.mock import Mock, patch
Expand All @@ -17,6 +18,21 @@
CERT_PATH = os.path.join(os.path.dirname(__file__), "certificate.pem")


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 = CertificateCredential(
"tenant-id", "client-id", CERT_PATH, policies=[ContentDecodePolicy(), policy], transport=Mock(send=send)
)

credential.get_token("scope")

assert policy.on_request.called


def test_request_url():
authority = "authority.com"
tenant_id = "expected_tenant"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@
from unittest.mock import Mock, patch
from urllib.parse import urlparse

from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
from azure.identity.aio import CertificateCredential
from helpers import urlsafeb64_decode, mock_response
from helpers import build_aad_response, urlsafeb64_decode, mock_response
import pytest

CERT_PATH = os.path.join(os.path.dirname(__file__), "certificate.pem")


@pytest.mark.asyncio
async def test_policies_configurable():
policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock())

async def send(*_, **__):
return mock_response(json_payload=build_aad_response(access_token="**"))

credential = CertificateCredential(
"tenant-id", "client-id", CERT_PATH, policies=[ContentDecodePolicy(), policy], transport=Mock(send=send)
)

await credential.get_token("scope")

assert policy.on_request.called


@pytest.mark.asyncio
async def test_request_url():
authority = "authority.com"
Expand Down
92 changes: 92 additions & 0 deletions sdk/identity/azure-identity/tests/test_client_secret_credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import time

from azure.core.credentials import AccessToken
from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
from azure.identity import ClientSecretCredential
from helpers import build_aad_response, mock_response, Request, validating_transport

try:
from unittest.mock import Mock
except ImportError: # python < 3.3
from mock import Mock # type: ignore


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_client_secret_credential():
client_id = "fake-client-id"
secret = "fake-client-secret"
tenant_id = "fake-tenant-id"
access_token = "***"

transport = validating_transport(
requests=[Request(url_substring=tenant_id, required_data={"client_id": client_id, "client_secret": secret})],
responses=[
mock_response(
json_payload={
"token_type": "Bearer",
"expires_in": 42,
"ext_expires_in": 42,
"access_token": access_token,
}
)
],
)

token = ClientSecretCredential(tenant_id, client_id, secret, transport=transport).get_token("scope")

# not validating expires_on because doing so requires monkeypatching time, and this is tested elsewhere
assert token.token == access_token


def test_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(
tenant_id="some-guid", client_id="client_id", client_secret="secret", 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import asyncio
import time
from unittest.mock import Mock

from azure.core.credentials import AccessToken
from azure.core.pipeline.policies import ContentDecodePolicy, SansIOHTTPPolicy
from azure.identity.aio import ClientSecretCredential
from helpers import async_validating_transport, build_aad_response, mock_response, Request

import pytest


@pytest.mark.asyncio
async def test_policies_configurable():
policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock())

async 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)
)

await credential.get_token("scope")

assert policy.on_request.called


@pytest.mark.asyncio
async def test_client_secret_credential():
client_id = "fake-client-id"
secret = "fake-client-secret"
tenant_id = "fake-tenant-id"
access_token = "***"

transport = async_validating_transport(
requests=[Request(url_substring=tenant_id, required_data={"client_id": client_id, "client_secret": secret})],
responses=[
mock_response(
json_payload={
"token_type": "Bearer",
"expires_in": 42,
"ext_expires_in": 42,
"access_token": access_token,
}
)
],
)

token = await ClientSecretCredential(
tenant_id=tenant_id, client_id=client_id, client_secret=secret, transport=transport
).get_token("scope")

# not validating expires_on because doing so requires monkeypatching time, and this is tested elsewhere
assert token.token == access_token


@pytest.mark.asyncio
async def test_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))
transport = Mock(send=asyncio.coroutine(mock_send))
scope = "scope"

credential = ClientSecretCredential("tenant-id", "client-id", "secret", transport=transport)

# get_token initially returns the expired token because the credential
# doesn't check whether tokens it receives from the service have expired
token = await 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 = await credential.get_token(scope)
assert token == valid_token
assert mock_send.call_count == 2
Loading