def test_oauth2_authorization_code_pkce_flow(token_cache_mock, httpx_mock: HTTPXMock): auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token") assert (get_header( httpx_mock, auth).get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA")
def test_oauth2_pkce_flow_get_code_is_sent_in_authorization_header_by_default( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "example", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter": "example_value", }, match_content= b"code_verifier=MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEx&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&response_type=code&code=SplxlOBeZQQYbYS6WxSbIA" ) assert (get_header( httpx_mock, auth).get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA") tab.assert_success( "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab." )
def test_oauth2_pkce_and_multiple_authentication_can_be_combined( token_cache, httpx_mock: HTTPXMock, browser_mock: BrowserMock, monkeypatch ): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) pkce_auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token" ) tab = browser_mock.add_response( opened_url="http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url="http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "example", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter": "example_value", }, ) api_key_auth = httpx_auth.HeaderApiKey("my_provided_api_key") api_key_auth2 = httpx_auth.HeaderApiKey( "my_provided_api_key2", header_name="X-Api-Key2" ) header = get_header(httpx_mock, pkce_auth & (api_key_auth & api_key_auth2)) assert header.get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA" assert header.get("X-Api-Key") == "my_provided_api_key" assert header.get("X-Api-Key2") == "my_provided_api_key2" tab.assert_success( "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab." )
def test_with_invalid_grant_request_invalid_scope_error( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code?nonce=123456", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&nonce=%5B%27123456%27%5D&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={"error": "invalid_scope"}, status_code=400, ) with pytest.raises(httpx_auth.InvalidGrantRequest) as exception_info: httpx.get("http://authorized_only", auth=auth) assert ( str(exception_info.value) == "invalid_scope: The requested scope is invalid, unknown, malformed, or " "exceeds the scope granted by the resource owner.") tab.assert_success( "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab." )
def test_header_value_must_contains_token(): with pytest.raises(Exception) as exception_info: httpx_auth.OAuth2AuthorizationCodePKCE("http://test_url", "http://test_url", header_value="Bearer token") assert str(exception_info.value ) == "header_value parameter must contains {token}."
def test_with_invalid_grant_request_invalid_client_error( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code?nonce=123456", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&nonce=%5B%27123456%27%5D&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={"error": "invalid_client"}, status_code=400, ) with pytest.raises(httpx_auth.InvalidGrantRequest) as exception_info: httpx.get("http://authorized_only", auth=auth) assert ( str(exception_info.value) == "invalid_client: Client authentication failed (e.g., unknown client, no " "client authentication included, or unsupported authentication method). The " "authorization server MAY return an HTTP 401 (Unauthorized) status code to " "indicate which HTTP authentication schemes are supported. If the client " 'attempted to authenticate via the "Authorization" request header field, the ' "authorization server MUST respond with an HTTP 401 (Unauthorized) status " 'code and include the "WWW-Authenticate" response header field matching the ' "authentication scheme used by the client.") tab.assert_success( "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab." )
def test_with_invalid_grant_request_invalid_request_error( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code?nonce=123456", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&nonce=%5B%27123456%27%5D&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={"error": "invalid_request"}, status_code=400, ) with pytest.raises(httpx_auth.InvalidGrantRequest) as exception_info: httpx.get("http://authorized_only", auth=auth) assert ( str(exception_info.value) == "invalid_request: The request is missing a required parameter, includes an " "unsupported parameter value (other than grant type), repeats a parameter, " "includes multiple credentials, utilizes more than one mechanism for " "authenticating the client, or is otherwise malformed.") tab.assert_success( "You are now authenticated on 163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de. You may close this tab." )
def test_with_invalid_token_request_invalid_request_error_and_error_description( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#error=invalid_request&error_description=desc", ) with pytest.raises(httpx_auth.InvalidGrantRequest) as exception_info: httpx.get("http://authorized_only", auth=auth) assert str(exception_info.value) == "invalid_request: desc" tab.assert_failure( "Unable to properly perform authentication: invalid_request: desc")
def test_oauth2_pkce_flow_get_code_custom_expiry(token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token", early_expiry=28) # Add a token that expires in 29 seconds, so should be considered as not expired when issuing the request token_cache._add_token( key= "163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de", token="2YotnFZFEjr1zCsicMWpAA", expiry=httpx_auth.oauth2_tokens._to_expiry(expires_in=29), ) assert (get_header( httpx_mock, auth).get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA")
def test_with_invalid_token_request_temporarily_unavailable_error( token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code", "http://provide_access_token") tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=code&state=163f0455b3e9cad3ca04254e5a0169553100d3aa0756c7964d897da316a695ffed5b4f46ef305094fd0a88cfe4b55ff257652015e4aa8f87b97513dba440f8de&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url="http://localhost:5000#error=temporarily_unavailable", ) with pytest.raises(httpx_auth.InvalidGrantRequest) as exception_info: httpx.get("http://authorized_only", auth=auth) assert ( str(exception_info.value) == "temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.)" ) tab.assert_failure( "Unable to properly perform authentication: temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server. (This error code is needed because a 503 Service Unavailable HTTP status code cannot be returned to the client via an HTTP redirect.)" )
def test_response_type_can_be_provided_in_url(token_cache, httpx_mock: HTTPXMock, monkeypatch, browser_mock: BrowserMock): monkeypatch.setattr(httpx_auth.authentication.os, "urandom", lambda x: b"1" * 63) auth = httpx_auth.OAuth2AuthorizationCodePKCE( "http://provide_code?response_type=my_code", "http://provide_access_token", response_type="not_used", ) tab = browser_mock.add_response( opened_url= "http://provide_code?response_type=%5B%27my_code%27%5D&state=b32e05720bd3722e0ac87bf72897a78b669a0810adf8da46b675793dcfe0f41a40f7d7fdda952bd73ea533a2462907d805adf8c1a162d51b99b2ddec0d411feb&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&code_challenge=5C_ph_KZ3DstYUc965SiqmKAA-ShvKF4Ut7daKd3fjc&code_challenge_method=S256", reply_url= "http://localhost:5000#code=SplxlOBeZQQYbYS6WxSbIA&state=b32e05720bd3722e0ac87bf72897a78b669a0810adf8da46b675793dcfe0f41a40f7d7fdda952bd73ea533a2462907d805adf8c1a162d51b99b2ddec0d411feb", ) httpx_mock.add_response( method="POST", url="http://provide_access_token", json={ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "example", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter": "example_value", }, match_content= b"code_verifier=MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEx&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F&response_type=my_code&code=SplxlOBeZQQYbYS6WxSbIA" ) assert (get_header( httpx_mock, auth).get("Authorization") == "Bearer 2YotnFZFEjr1zCsicMWpAA") tab.assert_success( "You are now authenticated on b32e05720bd3722e0ac87bf72897a78b669a0810adf8da46b675793dcfe0f41a40f7d7fdda952bd73ea533a2462907d805adf8c1a162d51b99b2ddec0d411feb. You may close this tab." )
def test_token_url_is_mandatory(): with pytest.raises(Exception) as exception_info: httpx_auth.OAuth2AuthorizationCodePKCE("http://test_url", "") assert str(exception_info.value) == "Token URL is mandatory."