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)] * 4, responses=[ get_discovery_response(authority), # instance discovery get_discovery_response(authority), # tenant discovery mock_response(status_code=404), # user realm discovery mock_response(json_payload=auth_response ), # token request following authenticate() ], ) credential = UsernamePasswordCredential( username=username, password="******", authority=environment, client_id=client_id, tenant_id=tenant_id, transport=transport, ) record = credential.authenticate(scopes=(scope, )) for auth_record in (record, credential.authentication_record): assert auth_record.authority == environment assert auth_record.home_account_id == object_id + "." + home_tenant assert auth_record.tenant_id == home_tenant assert auth_record.username == username # credential should have a cached access token for the scope passed to authenticate token = credential.get_token(scope) assert token.token == access_token
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_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_policies_configurable(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) transport = validating_transport( requests=[Request()] * 3, responses=[ # expected requests: discover tenant, start device code flow, poll for completion get_discovery_response(), mock_response( json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, } ), mock_response(json_payload=dict(build_aad_response(access_token="**"), scope="scope")), ], ) credential = DeviceCodeCredential( client_id="client-id", prompt_callback=Mock(), policies=[policy], transport=transport ) credential.get_token("scope") assert policy.on_request.called
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_user_agent(): client_id = "client-id" transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[ get_discovery_response(), mock_response( json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, }), mock_response(json_payload=dict(build_aad_response( access_token="**", id_token=build_id_token(aud=client_id)), scope="scope")), ], ) credential = DeviceCodeCredential(client_id=client_id, prompt_callback=Mock(), transport=transport) 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)] * 4, responses=[get_discovery_response(authority)] * 2 # instance and tenant discovery + [ mock_response( # start device code flow json_payload={ "device_code": "_", "user_code": "user-code", "verification_uri": "verification-uri", "expires_in": 42, } ), mock_response(json_payload=dict(auth_response, scope=scope)), # poll for completion ], ) credential = DeviceCodeCredential( client_id, prompt_callback=Mock(), # prevent credential from printing to stdout transport=transport, authority=environment, tenant_id=tenant_id, _cache=TokenCache(), ) record = credential.authenticate(scopes=(scope, )) # credential should have a cached access token for the scope used in authenticate token = credential.get_token(scope) assert token.token == access_token assert record.authority == environment assert record.home_account_id == object_id + "." + home_tenant assert record.tenant_id == home_tenant assert record.username == username
def send(request, **_): parsed = urlparse(request.url) tenant = parsed.path.split("/")[1] if "/oauth2/v2.0/token" not in parsed.path: return get_discovery_response("https://{}/{}".format( parsed.netloc, tenant)) return mock_response(json_payload=build_aad_response( access_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))
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))
def test_user_agent(): transport = validating_transport( requests=[Request()] * 2 + [Request(required_headers={"User-Agent": USER_AGENT})], responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", transport=transport) credential.get_token("scope")
def mock_send(request, **kwargs): if not request.body: return get_discovery_response() assert request.body["grant_type"] == "client_credentials" assert request.body["scope"] == expected_scope with open(cert_path, "rb") as cert_file: validate_jwt(request, client_id, cert_file.read(), expect_x5c=send_certificate_chain) return mock_response(json_payload=build_aad_response(access_token=access_token))
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(): policy = Mock(spec_set=SansIOHTTPPolicy, on_request=Mock()) transport = validating_transport( requests=[Request()] * 3, responses=[get_discovery_response()] * 2 + [mock_response(json_payload=build_aad_response(access_token="**", id_token=build_id_token()))], ) credential = UsernamePasswordCredential("client-id", "username", "password", policies=[policy], transport=transport) credential.get_token("scope") assert policy.on_request.called
def send(request, **_): parsed = urlparse(request.url) tenant_id = parsed.path.split("/")[1] if "/oauth2/v2.0/token" not in request.url: return get_discovery_response("https://{}/{}".format(parsed.netloc, tenant_id)) assert tenant_id in (default_tenant, second_tenant), 'unexpected tenant "{}"'.format(tenant_id) return mock_response( json_payload=build_aad_response( access_token=second_token if tenant_id == second_tenant else first_token, id_token_claims=id_token_claims(aud="...", iss="...", sub="..."), ) )
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 mock_send(request, **kwargs): if not request.body: return get_discovery_response() assert request.body["grant_type"] == "client_credentials" assert request.body["scope"] == expected_scope with open(cert_path, "rb") as cert_file: validate_jwt(request, client_id, cert_file.read()) return mock_response(json_payload={ "token_type": "Bearer", "expires_in": 42, "access_token": access_token })
def send(request, **_): # expecting only MSAL discovery requests assert request.method == "GET" return get_discovery_response()
def mock_send(request, **_): if not request.body: return get_discovery_response() assert request.url.startswith("https://localhost/" + expected_tenant_id) return mock_response(json_payload=build_aad_response(access_token="*"))
def send(request, **_): # expecting only the discovery requests triggered by creating an msal.PublicClientApplication # because the cache is empty--the credential shouldn't send a token request return get_discovery_response("https://localhost/tenant")
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 send(request, **_): assert "/oauth2/v2.0/token" not in request.url, 'mock "request_token" should prevent sending a token request' parsed = urlparse(request.url) tenant = parsed.path.split("/")[1] return get_discovery_response("https://{}/{}".format( parsed.netloc, tenant))
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 = get_discovery_response(endpoint=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=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)) credential = InteractiveBrowserCredential( authority=authority, tenant_id=tenant_id, client_id=client_id, server_class=server_class, transport=transport, instance_discovery=False, validate_authority=False, _cache=TokenCache(), ) # 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