def test_persistent_cache_linux(monkeypatch):
    """Credentials should use an unencrypted cache when encryption is unavailable and the user explicitly opts in.

    This test was written when Linux was the only platform on which encryption may not be available.
    """
    from azure.identity._persistent_cache import _load_persistent_cache

    for cls in ("FilePersistence", "LibsecretPersistence",
                "PersistedTokenCache"):
        monkeypatch.setattr(msal_extensions, cls, mock.Mock())

    _load_persistent_cache(TokenCachePersistenceOptions())
    assert msal_extensions.PersistedTokenCache.called_with(
        msal_extensions.LibsecretPersistence)
    msal_extensions.PersistedTokenCache.reset_mock()

    # when LibsecretPersistence's dependencies aren't available, constructing it raises ImportError
    msal_extensions.LibsecretPersistence = mock.Mock(side_effect=ImportError)

    # encryption unavailable, no unencrypted storage not allowed
    with pytest.raises(ValueError):
        _load_persistent_cache(TokenCachePersistenceOptions())

    # encryption unavailable, unencrypted storage allowed
    _load_persistent_cache(
        TokenCachePersistenceOptions(allow_unencrypted_storage=True))
    msal_extensions.PersistedTokenCache.called_with(
        msal_extensions.FilePersistence)
Ejemplo n.º 2
0
def test_multiple_scopes():
    """The credential should raise ValueError when get_token is called with more than one scope"""

    successful_probe = mock_response(status_code=400, json_payload={})
    transport = mock.Mock(send=mock.Mock(return_value=successful_probe))
    credential = ImdsCredential(transport=transport)

    with pytest.raises(ValueError):
        credential.get_token("one scope", "and another")
def test_retries_requests():
    """The client should retry token requests"""

    message = "can't connect"
    transport = mock.Mock(send=mock.Mock(side_effect=ServiceRequestError(message)))
    client = MsalClient(transport=transport)

    with pytest.raises(ServiceRequestError, match=message):
        client.post("https://localhost")
    assert transport.send.call_count > 1
    transport.send.reset_mock()

    with pytest.raises(ServiceRequestError, match=message):
        client.get("https://localhost")
    assert transport.send.call_count > 1
Ejemplo n.º 4
0
def test_deserializes_json_from_text():
    """The client should gracefully handle a response with a JSON body and content-type text/plain"""

    scope = "scope"
    now = int(time.time())
    expected_expires_on = now + 3600
    expected_token = "*"

    def send(request, **_):
        body = json.dumps({
            "access_token": expected_token,
            "expires_in": 3600,
            "expires_on": expected_expires_on,
            "resource": scope,
            "token_type": "Bearer",
        })
        return mock.Mock(
            status_code=200,
            headers={"Content-Type": "text/plain"},
            content_type="text/plain",
            text=lambda encoding=None: body,
        )

    client = ManagedIdentityClient(
        request_factory=lambda _, __: HttpRequest("GET", "http://localhost"),
        transport=mock.Mock(send=send))

    token = client.request_token(scope)
    assert token.expires_on == expected_expires_on
    assert token.token == expected_token
Ejemplo n.º 5
0
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 send(request, **_):
     return mock.Mock(
         status_code=200,
         headers={"Content-Type": content_type},
         content_type=content_type,
         text=lambda encoding=None: content,
     )
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
Ejemplo n.º 8
0
def test_retry():
    """ManagedIdentityClient should retry token requests"""

    message = "can't connect"
    transport = mock.Mock(send=mock.Mock(
        side_effect=ServiceRequestError(message)))
    request_factory = mock.Mock()

    client = ManagedIdentityClient(request_factory, transport=transport)

    for method in ("GET", "POST"):
        request_factory.return_value = HttpRequest(method, "https://localhost")
        with pytest.raises(ServiceRequestError, match=message):
            client.request_token("scope")
        assert transport.send.call_count > 1
        transport.send.reset_mock()
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")
Ejemplo n.º 10
0
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_retries():
    mock_response = mock.Mock(
        text=lambda encoding=None: b"{}",
        headers={"content-type": "application/json", "Retry-After": "0"},
        content_type="application/json",
    )
    mock_send = mock.Mock(return_value=mock_response)

    total_retries = PIPELINE_SETTINGS["retry_total"]

    for status_code in (404, 429, 500):
        mock_send.reset_mock()
        mock_response.status_code = status_code
        try:
            ImdsCredential(transport=mock.Mock(send=mock_send)).get_token("scope")
        except ClientAuthenticationError:
            pass
        # first call was availability probe, second the original request;
        # credential should have then exhausted retries for each of these status codes
        assert mock_send.call_count == 2 + total_retries
Ejemplo n.º 12
0
def test_cache():
    scope = "https://foo.bar"
    expired = "this token's expired"
    now = int(time.time())
    token_payload = {
        "access_token": expired,
        "refresh_token": "",
        "expires_in": 0,
        "expires_on": now - 300,  # expired 5 minutes ago
        "not_before": now,
        "resource": scope,
        "token_type": "Bearer",
    }

    mock_response = mock.Mock(
        text=lambda encoding=None: json.dumps(token_payload),
        headers={"content-type": "application/json"},
        status_code=200,
        content_type="application/json",
    )
    mock_send = mock.Mock(return_value=mock_response)

    credential = ImdsCredential(transport=mock.Mock(send=mock_send))
    token = credential.get_token(scope)
    assert token.token == expired
    assert mock_send.call_count == 2  # first request was probing for endpoint availability

    # calling get_token again should provoke another HTTP request
    good_for_an_hour = "this token's good for an hour"
    token_payload["expires_on"] = int(time.time()) + 3600
    token_payload["expires_in"] = 3600
    token_payload["access_token"] = good_for_an_hour
    token = credential.get_token(scope)
    assert token.token == good_for_an_hour
    assert mock_send.call_count == 3

    # get_token should return the cached token now
    token = credential.get_token(scope)
    assert token.token == good_for_an_hour
    assert mock_send.call_count == 3
Ejemplo n.º 13
0
 def send(request, **_):
     body = json.dumps({
         "access_token": expected_token,
         "expires_in": 3600,
         "expires_on": expected_expires_on,
         "resource": scope,
         "token_type": "Bearer",
     })
     return mock.Mock(
         status_code=200,
         headers={"Content-Type": "text/plain"},
         content_type="text/plain",
         text=lambda encoding=None: body,
     )
Ejemplo n.º 14
0
def test_unexpected_error():
    """The credential should raise ClientAuthenticationError when the endpoint returns an unexpected error"""

    error_message = "something went wrong"

    for code in range(401, 600):

        def send(request, **_):
            if "resource" not in request.query:
                # availability probe
                return mock_response(status_code=400, json_payload={})
            return mock_response(status_code=code, json_payload={"error": error_message})

        credential = ImdsCredential(transport=mock.Mock(send=send))

        with pytest.raises(ClientAuthenticationError) as ex:
            credential.get_token("scope")

        assert error_message in ex.value.message
Ejemplo n.º 15
0
def test_unexpected_content(content_type):
    content = "<html><body>not JSON</body></html>"

    def send(request, **_):
        return mock.Mock(
            status_code=200,
            headers={"Content-Type": content_type},
            content_type=content_type,
            text=lambda encoding=None: content,
        )

    client = ManagedIdentityClient(
        request_factory=lambda _, __: HttpRequest("GET", "http://localhost"),
        transport=mock.Mock(send=send))

    with pytest.raises(ClientAuthenticationError) as ex:
        client.request_token("scope")
    assert ex.value.response.text() == content

    if "json" not in content_type:
        assert content_type in ex.value.message
Ejemplo n.º 16
0
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")
Ejemplo n.º 17
0
def raise_called_process_error(return_code, output, cmd="..."):
    error = subprocess.CalledProcessError(return_code, cmd=cmd, output=output)
    return mock.Mock(side_effect=error)