Example #1
0
def test_multitenant_authentication_backcompat(cert_path, cert_password):
    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 = CertificateCredential(expected_tenant,
                                       "client-id",
                                       cert_path,
                                       password=cert_password,
                                       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

    token = credential.get_token("scope", tenant_id="un" + expected_tenant)
    assert token.token == expected_token
def test_certificate_credential(live_certificate):
    tenant_id = live_certificate["tenant_id"]
    client_id = live_certificate["client_id"]

    credential = CertificateCredential(tenant_id, client_id,
                                       live_certificate["cert_path"])
    get_token(credential)

    credential = CertificateCredential(
        tenant_id,
        client_id,
        live_certificate["cert_with_password_path"],
        password=live_certificate["password"])
    get_token(credential)

    credential = CertificateCredential(
        tenant_id, client_id, certificate_data=live_certificate["cert_bytes"])
    get_token(credential)

    credential = CertificateCredential(
        tenant_id,
        client_id,
        certificate_data=live_certificate["cert_with_password_bytes"],
        password=live_certificate["password"],
    )
    get_token(credential)
Example #3
0
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 = CertificateCredential("tenant", "client-id",
                                               PEM_CERT_PATH)
        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
Example #4
0
def test_request_url(cert_path, cert_password):
    authority = "authority.com"
    tenant_id = "expected_tenant"
    access_token = "***"

    def validate_url(url):
        parsed = urlparse(url)
        assert parsed.scheme == "https"
        assert parsed.netloc == authority
        assert parsed.path.startswith("/" + tenant_id)

    def mock_send(request, **kwargs):
        validate_url(request.url)
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": access_token
        })

    cred = CertificateCredential(tenant_id,
                                 "client-id",
                                 cert_path,
                                 password=cert_password,
                                 transport=Mock(send=mock_send),
                                 authority=authority)
    token = cred.get_token("scope")
    assert token.token == access_token
Example #5
0
def test_request_body():
    access_token = "***"
    authority = "authority.com"
    tenant_id = "tenant"

    def validate_url(url):
        scheme, netloc, path, _, _, _ = urlparse(url)
        assert scheme == "https"
        assert netloc == authority
        assert path.startswith("/" + tenant_id)

    def mock_send(request, **kwargs):
        jwt = request.body["client_assertion"]
        header, payload, signature = (urlsafeb64_decode(s)
                                      for s in jwt.split("."))
        claims = json.loads(payload.decode("utf-8"))
        validate_url(claims["aud"])
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": access_token
        })

    cred = CertificateCredential(tenant_id,
                                 "client_id",
                                 CERT_PATH,
                                 transport=Mock(send=mock_send),
                                 authority=authority)
    token = cred.get_token("scope")
    assert token.token == access_token
Example #6
0
def test_enable_persistent_cache(cert_path, cert_password):
    """the credential should use the persistent cache only when given enable_persistent_cache=True"""

    persistent_cache = "azure.identity._internal.persistent_cache"
    required_arguments = ("tenant-id", "client-id", cert_path)

    # credential should default to an in memory cache
    raise_when_called = Mock(side_effect=Exception("credential shouldn't attempt to load a persistent cache"))
    with patch(persistent_cache + "._load_persistent_cache", raise_when_called):
        CertificateCredential(*required_arguments, password=cert_password)

        # allowing an unencrypted cache doesn't count as opting in to the persistent cache
        CertificateCredential(*required_arguments, password=cert_password, allow_unencrypted_cache=True)

    # keyword argument opts in to persistent cache
    with patch(persistent_cache + ".msal_extensions") as mock_extensions:
        CertificateCredential(*required_arguments, password=cert_password, enable_persistent_cache=True)
    assert mock_extensions.PersistedTokenCache.call_count == 1

    # opting in on an unsupported platform raises an exception
    with patch(persistent_cache + ".sys.platform", "commodore64"):
        with pytest.raises(NotImplementedError):
            CertificateCredential(*required_arguments, password=cert_password, enable_persistent_cache=True)
        with pytest.raises(NotImplementedError):
            CertificateCredential(
                *required_arguments, password=cert_password, enable_persistent_cache=True, allow_unencrypted_cache=True
            )
Example #7
0
def test_non_rsa_key():
    """The credential should raise ValueError when given a cert without an RSA private key"""
    with pytest.raises(ValueError, match=".*RS256.*"):
        CertificateCredential("tenant-id", "client-id", EC_CERT_PATH)
    with pytest.raises(ValueError, match=".*RS256.*"):
        CertificateCredential("tenant-id",
                              "client-id",
                              certificate_data=open(EC_CERT_PATH, "rb").read())
Example #8
0
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
Example #9
0
    def get_credentials(self):
        """ Get credentials """
        tenant_id = self.config.get('azure.tenant_id')
        client_id = self.config.get('azure.client_id')
        client_secret = self.config.get('azure.client_secret')
        certificate_path = self.config.get('azure.client_certificate_path')
        username = self.config.get('azure.username')
        password = self.config.get('azure.password')

        if tenant_id and client_id and client_secret:
            return ClientSecretCredential(tenant_id=tenant_id,
                                          client_id=client_id,
                                          client_secret=client_secret)

        if tenant_id and client_id and certificate_path:
            return CertificateCredential(tenant_id=tenant_id,
                                         client_id=client_id,
                                         certificate_path=certificate_path)

        if client_id and username and password:
            return UsernamePasswordCredential(client_id=client_id,
                                              username=username,
                                              password=password)

        return DefaultAzureCredential()
Example #10
0
def test_request_url(cert_path, cert_password, 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})

    cred = CertificateCredential(
        tenant_id, "client-id", cert_path, password=cert_password, transport=Mock(send=mock_send), authority=authority
    )
    token = cred.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 = CertificateCredential(
            tenant_id, "client-id", cert_path, password=cert_password, transport=Mock(send=mock_send)
        )
        credential.get_token("scope")
    assert token.token == access_token
Example #11
0
def test_persistent_cache_multiple_clients(cert_path, cert_password):
    """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 = validating_transport(
        requests=[Request()], responses=[mock_response(json_payload=build_aad_response(access_token=access_token_a))]
    )
    transport_b = 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 = CertificateCredential(
            "tenant", "client-a", cert_path, password=cert_password, enable_persistent_cache=True, transport=transport_a
        )
        assert mock_cache_loader.call_count == 1, "credential should load the persistent cache"
        credential_b = CertificateCredential(
            "tenant", "client-b", cert_path, password=cert_password, 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 == 1

    # 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 == 1
Example #12
0
def test_persistent_cache_multiple_clients(cert_path, cert_password):
    """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.msal_credentials._load_persistent_cache"
    ) as mock_cache_loader:
        mock_cache_loader.return_value = Mock(wraps=cache)
        credential_a = CertificateCredential(
            "tenant",
            "client-a",
            cert_path,
            password=cert_password,
            transport=transport_a,
            cache_persistence_options=TokenCachePersistenceOptions(),
        )
        assert mock_cache_loader.call_count == 1, "credential should load the persistent cache"

        credential_b = CertificateCredential(
            "tenant",
            "client-b",
            cert_path,
            password=cert_password,
            transport=transport_b,
            cache_persistence_options=TokenCachePersistenceOptions(),
        )
        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

    assert len(cache.find(TokenCache.CredentialType.ACCESS_TOKEN)) == 2
def test_request_url(cert_path, cert_password):
    authority = "authority.com"
    tenant_id = "expected_tenant"
    access_token = "***"

    def validate_url(url):
        parsed = urlparse(url)
        assert parsed.scheme == "https"
        assert parsed.netloc == authority
        assert parsed.path.startswith("/" + tenant_id)

    def mock_send(request, **kwargs):
        validate_url(request.url)
        return mock_response(json_payload={
            "token_type": "Bearer",
            "expires_in": 42,
            "access_token": access_token
        })

    cred = CertificateCredential(tenant_id,
                                 "client-id",
                                 cert_path,
                                 password=cert_password,
                                 transport=Mock(send=mock_send),
                                 authority=authority)
    token = cred.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 = CertificateCredential(tenant_id,
                                           "client-id",
                                           cert_path,
                                           password=cert_password,
                                           transport=Mock(send=mock_send))
        credential.get_token("scope")
    assert token.token == access_token
Example #14
0
def test_multitenant_authentication_backcompat(cert_path, cert_password):
    """When allow_multitenant_authentication is True, the credential should respect get_token(tenant_id=...)"""

    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 = CertificateCredential(
        expected_tenant, "client-id", cert_path, password=cert_password, 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"}, clear=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"
Example #15
0
def test_multitenant_authentication(cert_path, cert_password):
    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 = CertificateCredential(
        first_tenant,
        "client-id",
        cert_path,
        password=cert_password,
        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