def test_user_agent():
    client_id = "client-id"
    transport = validating_transport(
        requests=[
            Request(),
            Request(required_headers={"User-Agent": USER_AGENT})
        ],
        responses=[
            get_discovery_response(),
            mock_response(json_payload=build_aad_response(
                access_token="**", id_token=build_id_token(aud=client_id))),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    oauth_state = "oauth-state"
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    credential = InteractiveBrowserCredential(client_id=client_id,
                                              transport=transport,
                                              _server_class=server_class,
                                              _cache=TokenCache())

    with patch("azure.identity._credentials.browser.uuid.uuid4",
               lambda: oauth_state):
        credential.get_token("scope")
def test_no_browser():
    transport = validating_transport(requests=[Request()] * 2, responses=[get_discovery_response()] * 2)
    credential = InteractiveBrowserCredential(
        client_id="client-id", server_class=Mock(), transport=transport, _cache=TokenCache()
    )
    with pytest.raises(ClientAuthenticationError, match=r".*browser.*"):
        credential.get_token("scope")
def test_cannot_bind_port():
    """get_token should raise CredentialUnavailableError when the redirect listener can't bind a port"""

    credential = InteractiveBrowserCredential(_server_class=Mock(
        side_effect=socket.error))
    with pytest.raises(CredentialUnavailableError):
        credential.get_token("scope")
def test_timeout():
    """get_token should raise ClientAuthenticationError when the server times out without receiving a redirect"""

    timeout = 0.01

    class GuaranteedTimeout(AuthCodeRedirectServer, object):
        def handle_request(self):
            time.sleep(timeout + 0.01)
            super(GuaranteedTimeout, self).handle_request()

    # mock transport handles MSAL's tenant discovery
    transport = Mock(send=lambda _, **__: mock_response(
        json_payload={
            "authorization_endpoint": "https://a/b",
            "token_endpoint": "https://a/b"
        }))

    credential = InteractiveBrowserCredential(timeout=timeout,
                                              transport=transport,
                                              _cache=TokenCache(),
                                              _server_class=GuaranteedTimeout)

    with patch(WEBBROWSER_OPEN, lambda _: True):
        with pytest.raises(ClientAuthenticationError) as ex:
            credential.get_token("scope")
    assert "timed out" in ex.value.message.lower()
Beispiel #5
0
def test_interactive_credential_timeout():
    # mock transport handles MSAL's tenant discovery
    transport = Mock(send=lambda _, **__: mock_response(
        json_payload={
            "authorization_endpoint": "https://a/b",
            "token_endpoint": "https://a/b"
        }))

    # mock local server blocks long enough to exceed the timeout
    timeout = 0.01
    server_instance = Mock(
        wait_for_redirect=functools.partial(time.sleep, timeout + 0.01))
    server_class = Mock(return_value=server_instance)

    credential = InteractiveBrowserCredential(
        client_id="guid",
        client_secret="secret",
        server_class=server_class,
        timeout=timeout,
        transport=transport,
        instance_discovery=
        False,  # kwargs are passed to MSAL; this one prevents an AAD verification request
    )

    with pytest.raises(ClientAuthenticationError) as ex:
        credential.get_token("scope")
    assert "timed out" in ex.value.message.lower()
def test_policies_configurable():
    policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock())
    client_id = "client-id"
    transport = validating_transport(
        requests=[Request()] * 2,
        responses=[
            get_discovery_response(),
            mock_response(json_payload=build_aad_response(
                access_token="**", id_token=build_id_token(aud=client_id))),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    oauth_state = "oauth-state"
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    credential = InteractiveBrowserCredential(policies=[policy],
                                              client_id=client_id,
                                              transport=transport,
                                              _server_class=server_class,
                                              _cache=TokenCache())

    with patch("azure.identity._credentials.browser.uuid.uuid4",
               lambda: oauth_state):
        credential.get_token("scope")

    assert policy.on_request.called
def test_browser_credential():
    transport = Mock(wraps=RequestsTransport())
    credential = InteractiveBrowserCredential(transport=transport)
    scope = "https://management.azure.com/.default"  # N.B. this is valid only in Public Cloud

    record = credential.authenticate(scopes=(scope, ))
    assert record.authority
    assert record.home_account_id
    assert record.tenant_id
    assert record.username

    # credential should have a cached access token for the scope used in authenticate
    with patch(
            WEBBROWSER_OPEN,
            Mock(side_effect=Exception(
                "credential should authenticate silently"))):
        token = credential.get_token(scope)
    assert token.token

    credential = InteractiveBrowserCredential(transport=transport)
    token = credential.get_token(scope)
    assert token.token

    with patch(
            WEBBROWSER_OPEN,
            Mock(side_effect=Exception(
                "credential should authenticate silently"))):
        second_token = credential.get_token(scope)
    assert second_token.token == token.token

    # every request should have the correct User-Agent
    for call in transport.send.call_args_list:
        args, _ = call
        request = args[0]
        assert request.headers["User-Agent"] == USER_AGENT
def test_disable_automatic_authentication():
    """When configured for strict silent auth, the credential should raise when silent auth fails"""

    transport = Mock(send=Mock(side_effect=Exception("no request should be sent")))
    credential = InteractiveBrowserCredential(disable_automatic_authentication=True, transport=transport)

    with patch(WEBBROWSER_OPEN, Mock(side_effect=Exception("credential shouldn't try interactive authentication"))):
        with pytest.raises(AuthenticationRequiredError):
            credential.get_token("scope")
def test_cannot_bind_redirect_uri():
    """When a user specifies a redirect URI, the credential shouldn't attempt to bind another"""

    server = Mock(side_effect=socket.error)
    credential = InteractiveBrowserCredential(redirect_uri="http://localhost:42", _server_class=server)

    with pytest.raises(CredentialUnavailableError):
        credential.get_token("scope")

    server.assert_called_once_with("localhost", 42, timeout=ANY)
def test_claims_challenge():
    """get_token and authenticate should pass any claims challenge to MSAL token acquisition APIs"""

    expected_claims = '{"access_token": {"essential": "true"}'

    auth_code_response = {"code": "authorization-code", "state": ["..."]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    msal_acquire_token_result = dict(
        build_aad_response(access_token="**", id_token=build_id_token()),
        id_token_claims=id_token_claims("issuer",
                                        "subject",
                                        "audience",
                                        upn="upn"),
    )

    transport = Mock(send=Mock(side_effect=Exception(
        "this test mocks MSAL, so no request should be sent")))
    credential = InteractiveBrowserCredential(_server_class=server_class,
                                              transport=transport)
    with patch.object(InteractiveBrowserCredential,
                      "_get_app") as get_mock_app:
        msal_app = get_mock_app()
        msal_app.initiate_auth_code_flow.return_value = {
            "auth_uri": "http://localhost"
        }
        msal_app.acquire_token_by_auth_code_flow.return_value = msal_acquire_token_result

        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential.authenticate(scopes=["scope"], claims=expected_claims)

        assert msal_app.acquire_token_by_auth_code_flow.call_count == 1
        args, kwargs = msal_app.acquire_token_by_auth_code_flow.call_args
        assert kwargs["claims_challenge"] == expected_claims

        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential.get_token("scope", claims=expected_claims)

        assert msal_app.acquire_token_by_auth_code_flow.call_count == 2
        args, kwargs = msal_app.acquire_token_by_auth_code_flow.call_args
        assert kwargs["claims_challenge"] == expected_claims

        msal_app.get_accounts.return_value = [{
            "home_account_id":
            credential._auth_record.home_account_id
        }]
        msal_app.acquire_token_silent_with_error.return_value = msal_acquire_token_result
        credential.get_token("scope", claims=expected_claims)

        assert msal_app.acquire_token_silent_with_error.call_count == 1
        args, kwargs = msal_app.acquire_token_silent_with_error.call_args
        assert kwargs["claims_challenge"] == expected_claims
def test_no_browser():
    """The credential should raise CredentialUnavailableError when it can't open a browser"""

    transport = validating_transport(requests=[Request()] * 2,
                                     responses=[get_discovery_response()] * 2)
    credential = InteractiveBrowserCredential(client_id="client-id",
                                              _server_class=Mock(),
                                              transport=transport)
    with patch(InteractiveBrowserCredential.__module__ + "._open_browser",
               lambda _: False):
        with pytest.raises(CredentialUnavailableError, match=r".*browser.*"):
            credential.get_token("scope")
def test_policies_configurable():
    # the policy raises an exception so this test can run without authenticating i.e. opening a browser
    expected_message = "test_policies_configurable"
    policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock(side_effect=Exception(expected_message)))

    credential = InteractiveBrowserCredential(policies=[policy])

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

    assert expected_message in ex.value.message
    assert policy.on_request.called
def test_redirect_uri():
    """The credential should configure the redirect server to use a given redirect_uri"""

    expected_hostname = "localhost"
    expected_port = 42424
    expected_message = "test_redirect_uri"
    server = Mock(side_effect=Exception(expected_message))  # exception prevents this test actually authenticating
    credential = InteractiveBrowserCredential(
        redirect_uri="htps://{}:{}".format(expected_hostname, expected_port), _server_class=server
    )
    with pytest.raises(ClientAuthenticationError) as ex:
        credential.get_token("scope")

    assert expected_message in ex.value.message
    server.assert_called_once_with(expected_hostname, expected_port, timeout=ANY)
Beispiel #14
0
    def __init__(self, vault_url, credential=None):
        '''
        
        Parameters:
        
            vault_url (str) – RL of the vault the client will access. This is also called the vault's "DNS Name".
            credential (str) –  An object which can provide an access token for the vault, such as a service principal credential
            
        Example:
        
        .. code-block:: python
            
            import pynomial as pyn
            
            client = pyn.KeyVaultclient(vault_url='https://keyvaulttestinstance.vault.azure.net/')
            retrieved_secret = client.get_secret('MyPassword')
            retrieved_secret.value


        .. code-block:: python
            
            import pynomial as pyn

            credential = pyn.SPCredential()
            client = pyn.KeyVaultclient(vault_url='https://keyvaulttestinstance.vault.azure.net/', credential=credential)
            retrieved_secret = client.get_secret('MyPassword').value
            
        '''
        from azure.identity import InteractiveBrowserCredential

        if credential is None:
            credential = InteractiveBrowserCredential()
            self.token = credential.get_token()

        self = super().__init__(vault_url=vault_url, credential=credential)
Beispiel #15
0
def test_no_browser():
    discovery_response = mock_response(
        json_payload={
            name: "https://foo/bar"
            for name in ("authorization_endpoint", "token_endpoint",
                         "tenant_discovery_endpoint")
        })
    transport = validating_transport(
        requests=[Request()] * 2,
        responses=[discovery_response, discovery_response])
    credential = InteractiveBrowserCredential(client_id="client-id",
                                              client_secret="secret",
                                              server_class=Mock(),
                                              transport=transport)
    with pytest.raises(ClientAuthenticationError, match=r".*browser.*"):
        credential.get_token("scope")
def test_authenticate():
    client_id = "client-id"
    environment = "localhost"
    issuer = "https://" + environment
    tenant_id = "some-tenant"
    authority = issuer + "/" + tenant_id

    access_token = "***"
    scope = "scope"

    # mock AAD response with id token
    object_id = "object-id"
    home_tenant = "home-tenant-id"
    username = "******"
    id_token = build_id_token(aud=client_id, iss=issuer, object_id=object_id, tenant_id=home_tenant, username=username)
    auth_response = build_aad_response(
        uid=object_id, utid=home_tenant, access_token=access_token, refresh_token="**", id_token=id_token
    )

    transport = validating_transport(
        requests=[Request(url_substring=issuer)] * 3,
        responses=[get_discovery_response(authority)] * 2 + [mock_response(json_payload=auth_response)],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    oauth_state = "state"
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response))

    with patch(InteractiveBrowserCredential.__module__ + ".uuid.uuid4", lambda: oauth_state):
        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential = InteractiveBrowserCredential(
                _cache=TokenCache(),
                authority=environment,
                client_id=client_id,
                server_class=server_class,
                tenant_id=tenant_id,
                transport=transport,
            )
            record = credential._authenticate(scopes=(scope,))

    assert record.authority == environment
    assert record.home_account_id == object_id + "." + home_tenant
    assert record.tenant_id == home_tenant
    assert record.username == username

    # credential should have a cached access token for the scope used in authenticate
    with patch(WEBBROWSER_OPEN, Mock(side_effect=Exception("credential should authenticate silently"))):
        token = credential.get_token(scope)
    assert token.token == access_token
def test_wsl_fallback(uname, is_wsl):
    """the credential should invoke powershell.exe to open a browser in WSL when webbrowser.open fails"""

    auth_uri = "http://localhost"
    expected_access_token = "**"
    msal_acquire_token_result = dict(
        build_aad_response(access_token=expected_access_token,
                           id_token=build_id_token()),
        id_token_claims=id_token_claims("issuer",
                                        "subject",
                                        "audience",
                                        upn="upn"),
    )
    msal_app = Mock(
        initiate_auth_code_flow=Mock(return_value={"auth_uri": auth_uri}),
        acquire_token_by_auth_code_flow=Mock(
            return_value=msal_acquire_token_result),
    )

    transport = Mock(send=Mock(side_effect=Exception(
        "this test mocks MSAL, so no request should be sent")))
    credential = InteractiveBrowserCredential(_server_class=Mock(),
                                              transport=transport)

    with patch(InteractiveBrowserCredential.__module__ +
               ".subprocess.call") as subprocess_call:
        subprocess_call.return_value = 0
        with patch(InteractiveBrowserCredential.__module__ + ".platform.uname",
                   lambda: uname):
            with patch.object(InteractiveBrowserCredential, "_get_app",
                              lambda _: msal_app):
                with patch(WEBBROWSER_OPEN, lambda _: False):
                    try:
                        token = credential.get_token("scope")
                    except CredentialUnavailableError:
                        assert not is_wsl, "credential should invoke powershell.exe in WSL"
                        return

    assert is_wsl, "credential should raise CredentialUnavailableError when not in WSL"
    assert token.token == expected_access_token
    assert subprocess_call.call_count == 1
    args, kwargs = subprocess_call.call_args
    assert args[0][0] == "powershell.exe"
    assert auth_uri in args[0][-1]
    if platform.python_version() >= "3.3":
        assert "timeout" in kwargs
def test_interactive_credential():
    oauth_state = "state"
    expected_token = "access-token"

    transport = validating_transport(
        requests=[Request()] *
        2,  # not validating requests because they're formed by MSAL
        responses=[
            # expecting tenant discovery then a token request
            mock_response(
                json_payload={
                    "authorization_endpoint": "https://a/b",
                    "token_endpoint": "https://a/b"
                }),
            mock_response(
                json_payload={
                    "access_token": expected_token,
                    "expires_in": 42,
                    "token_type": "Bearer",
                    "ext_expires_in": 42,
                }),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    credential = InteractiveBrowserCredential(
        client_id="guid",
        client_secret="secret",
        server_class=server_class,
        transport=transport,
        instance_discovery=
        False,  # kwargs are passed to MSAL; this one prevents an AAD verification request
    )

    # ensure the request beginning the flow has a known state value
    with patch("azure.identity._credentials.browser.uuid.uuid4",
               lambda: oauth_state):
        token = credential.get_token("scope")
    assert token.token == expected_token
Beispiel #19
0
def test_interactive_credential(mock_open):
    mock_open.side_effect = _validate_auth_request_url
    oauth_state = "state"
    client_id = "client-id"
    expected_refresh_token = "refresh-token"
    expected_token = "access-token"
    expires_in = 3600
    authority = "authority"
    tenant_id = "tenant_id"
    endpoint = "https://{}/{}".format(authority, tenant_id)

    discovery_response = mock_response(
        json_payload={
            name: endpoint
            for name in ("authorization_endpoint", "token_endpoint",
                         "tenant_discovery_endpoint")
        })
    transport = validating_transport(
        requests=[Request(url_substring=endpoint)] * 3 + [
            Request(authority=authority,
                    url_substring=endpoint,
                    required_data={"refresh_token": expected_refresh_token})
        ],
        responses=[
            discovery_response,  # instance discovery
            discovery_response,  # tenant discovery
            mock_response(json_payload=build_aad_response(
                access_token=expected_token,
                expires_in=expires_in,
                refresh_token=expected_refresh_token,
                uid="uid",
                utid="utid",
                token_type="Bearer",
            )),
            mock_response(
                json_payload=build_aad_response(access_token=expected_token,
                                                expires_in=expires_in,
                                                token_type="Bearer")),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    credential = InteractiveBrowserCredential(
        authority=authority,
        tenant_id=tenant_id,
        client_id=client_id,
        client_secret="secret",
        server_class=server_class,
        transport=transport,
        instance_discovery=False,
        validate_authority=False,
    )

    # The credential's auth code request includes a uuid which must be included in the redirect. Patching to
    # set the uuid requires less code here than a proper mock server.
    with patch("azure.identity._credentials.browser.uuid.uuid4",
               lambda: oauth_state):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # token should be cached, get_token shouldn't prompt again
    token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # As of MSAL 1.0.0, applications build a new client every time they redeem a refresh token.
    # Here we patch the private method they use for the sake of test coverage.
    # TODO: this will probably break when this MSAL behavior changes
    app = credential._get_app()
    app._build_client = lambda *_: app.client  # pylint:disable=protected-access
    now = time.time()

    # expired access token -> credential should use refresh token instead of prompting again
    with patch("time.time", lambda: now + expires_in):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # ensure all expected requests were sent
    assert transport.send.call_count == 4
Beispiel #20
0
    def _get_creds_from_login(self, authentication, reason=None):

        # use normal Key Value
        from azure.keyvault.secrets import SecretClient

        if authentication == "auto":
            authentication = "browser" if pc_utils.has_gui() else "device-code"

        if authentication == "browser":
            console.print("authenticating with azure thru browser... ",
                          flush=True,
                          end="")
            from azure.identity import InteractiveBrowserCredential
            if self.azure_tenant_id is not None:
                credential = InteractiveBrowserCredential(
                    tenant_id=self.azure_tenant_id)
            else:
                credential = InteractiveBrowserCredential()
        elif authentication == "device-code":
            # console.print("authenticating with azure thru device code... ", flush=True, end="")
            from azure.identity import DeviceCodeCredential
            from azure.identity._constants import AZURE_CLI_CLIENT_ID

            console.print(
                "using device-code authorization (Azure AD currently requires 2-4 authenications here)"
            )
            if self.azure_tenant_id is not None:
                credential = DeviceCodeCredential(
                    tenant_id=self.azure_tenant_id,
                    client_id=AZURE_CLI_CLIENT_ID)
            else:
                credential = DeviceCodeCredential(
                    client_id=AZURE_CLI_CLIENT_ID)
        else:
            errors.syntax_error(
                "unrecognized authentication type '{}'".format(authentication))

        new_creds = True
        outer_token = credential.get_token()
        token = outer_token.token

        # expires = outer_token[1]
        # elapsed = expires - time.time()
        #print(" [new token expires in {:.2f} mins] ".format(elapsed/60), end="")

        # get keys from keyvault
        self.client = SecretClient(self.vault_url, credential=credential)
        key_text = self.get_secret_live("xt-keys")
        console.print("authenticated successfully", flush=True)

        #xt_client_cert = self.get_secret_live("xt-clientcert")
        xt_server_cert = self.get_secret_live("xt-servercert")

        # write all our creds to self.keys
        self.apply_creds(key_text)
        self.keys["xt_server_cert"] = xt_server_cert

        self.keys["object_id"] = self.get_me_graph_property(token, "id")

        # return creds as json string
        return json.dumps(self.keys)
def test_no_browser():
    transport = validating_transport(requests=[Request()] * 2, responses=[get_discovery_response()] * 2)
    credential = InteractiveBrowserCredential(client_id="client-id", _server_class=Mock(), transport=transport)
    with pytest.raises(ClientAuthenticationError, match=r".*browser.*"):
        with patch(WEBBROWSER_OPEN, lambda _: False):
            credential.get_token("scope")
def test_interactive_credential(mock_open, redirect_url):
    mock_open.side_effect = _validate_auth_request_url
    oauth_state = "state"
    client_id = "client-id"
    expected_refresh_token = "refresh-token"
    expected_token = "access-token"
    expires_in = 3600
    authority = "authority"
    tenant_id = "tenant-id"
    endpoint = "https://{}/{}".format(authority, tenant_id)

    transport = msal_validating_transport(
        endpoint="https://{}/{}".format(authority, tenant_id),
        requests=[Request(url_substring=endpoint)] + [
            Request(authority=authority,
                    url_substring=endpoint,
                    required_data={"refresh_token": expected_refresh_token})
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=expected_token,
                expires_in=expires_in,
                refresh_token=expected_refresh_token,
                uid="uid",
                utid=tenant_id,
                id_token=build_id_token(aud=client_id,
                                        object_id="uid",
                                        tenant_id=tenant_id,
                                        iss=endpoint),
                token_type="Bearer",
            )),
            mock_response(
                json_payload=build_aad_response(access_token=expected_token,
                                                expires_in=expires_in,
                                                token_type="Bearer")),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    args = {
        "authority": authority,
        "tenant_id": tenant_id,
        "client_id": client_id,
        "transport": transport,
        "_cache": TokenCache(),
        "_server_class": server_class,
    }
    if redirect_url:  # avoid passing redirect_url=None
        args["redirect_uri"] = redirect_url

    credential = InteractiveBrowserCredential(**args)

    # The credential's auth code request includes a uuid which must be included in the redirect. Patching to
    # set the uuid requires less code here than a proper mock server.
    with patch("azure.identity._credentials.browser.uuid.uuid4",
               lambda: oauth_state):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1
    assert server_class.call_count == 1

    if redirect_url:
        server_class.assert_called_once_with(redirect_url, timeout=ANY)

    # token should be cached, get_token shouldn't prompt again
    token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1
    assert server_class.call_count == 1

    # expired access token -> credential should use refresh token instead of prompting again
    now = time.time()
    with patch("time.time", lambda: now + expires_in):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # ensure all expected requests were sent
    assert transport.send.call_count == 4