async def test_client_success_call_oauth(fs, monkeypatch): oauth_client = Client({ "orgUrl": ORG_URL, "authorizationMode": "PrivateKey", "clientId": CLIENT_ID, "scopes": SCOPES, "privateKey": PRIVATE_KEY }) request_executor = oauth_client.get_request_executor() monkeypatch.setattr(OAuth, 'get_access_token', mocks.mock_access_token) req, err = await request_executor.create_request("GET", GET_USERS_CALL, {}, {}) req, res_details, resp_body, error = await oauth_client\ .get_request_executor().fire_request(req) assert error is None assert "User-Agent" in req["headers"] assert "Authorization" in req["headers"] assert req["headers"]["Authorization"].startswith("Bearer") assert res_details.status == 200 assert "application/json" in res_details.headers["Content-Type"] assert resp_body is not None
def test_known_sign_on_mode(): response = copy.deepcopy(SAML_APP_RESP_DICT) config = { "orgUrl": "https://test_org.okta.com", "token": "test_token", "requestExecutor": MockRequestExecutor, } client = Client(config) # check list applications client._request_executor.set_response([response]) event_loop = asyncio.get_event_loop() result, resp, err = event_loop.run_until_complete( client.list_applications()) assert type(result[0]) == SamlApplication assert result[0].as_dict() == EXPECTED_SAML_APP_AS_DICT # check get application client._request_executor.set_response(response) event_loop = asyncio.get_event_loop() result, resp, err = event_loop.run_until_complete( client.get_application("test_id")) assert type(result) == SamlApplication assert result.as_dict() == EXPECTED_SAML_APP_AS_DICT
def test_constructor_env_vars_PK(): authorizationMode = "PrivateKey" org_url = "https://test.okta.com" client_id = "clientID" scopes = "scope1,scope2,scope3" private_key = "private key" os.environ["OKTA_CLIENT_AUTHORIZATIONMODE"] = authorizationMode os.environ["OKTA_CLIENT_ORGURL"] = org_url os.environ["OKTA_CLIENT_CLIENTID"] = client_id os.environ["OKTA_CLIENT_SCOPES"] = scopes os.environ["OKTA_CLIENT_PRIVATEKEY"] = private_key client = OktaClient() loaded_config = client.get_config() os.environ.pop("OKTA_CLIENT_ORGURL") os.environ.pop("OKTA_CLIENT_AUTHORIZATIONMODE") os.environ.pop("OKTA_CLIENT_CLIENTID") os.environ.pop("OKTA_CLIENT_SCOPES") os.environ.pop("OKTA_CLIENT_PRIVATEKEY") assert authorizationMode == loaded_config['client']['authorizationMode'] assert org_url == loaded_config['client']['orgUrl'] assert client_id == loaded_config['client']['clientId'] assert scopes.split(',') == loaded_config['client']['scopes'] assert private_key == loaded_config['client']['privateKey']
def test_constructor_precedence_highest_rank_local_yaml(fs): # Setup Global config fs.pause() global_sample = os.path.join(os.path.dirname( __file__), "files", "SSWS-sample-global.yaml") with open(global_sample) as file: global_config = yaml.load(file, Loader=yaml.SafeLoader) global_org_url = global_config["okta"]["client"]["orgUrl"] global_token = global_config["okta"]["client"]["token"] fs.resume() fs.create_file(_GLOBAL_YAML_PATH, contents=yaml.dump(global_config)) # Setup Local config fs.pause() local_sample = os.path.join(os.path.dirname( __file__), "files", "SSWS-sample-local.yaml") with open(local_sample) as file: local_config = yaml.load(file, Loader=yaml.SafeLoader) local_org_url = local_config["okta"]["client"]["orgUrl"] local_token = local_config["okta"]["client"]["token"] fs.resume() fs.create_file(_LOCAL_YAML_PATH, contents=yaml.dump(local_config)) # Create client and validate values client = OktaClient() loaded_config = client.get_config() assert local_org_url == loaded_config['client']['orgUrl'] assert local_token == loaded_config['client']['token'] assert local_org_url != global_org_url assert local_token != global_token assert global_org_url != loaded_config['client']['orgUrl'] assert global_token != loaded_config['client']['token']
def test_constructor_precedence_highest_rank_env_vars(fs): # Setup Local config fs.pause() local_sample = os.path.join(os.path.dirname( __file__), "files", "SSWS-sample-local.yaml") with open(local_sample) as file: local_config = yaml.load(file, Loader=yaml.SafeLoader) local_org_url = local_config["okta"]["client"]["orgUrl"] local_token = local_config["okta"]["client"]["token"] fs.resume() fs.create_file(_LOCAL_YAML_PATH, contents=yaml.dump(local_config)) # Setup env. vars env_org_url = "https://test.env.okta.com" env_token = "envTOKEN" os.environ["OKTA_CLIENT_ORGURL"] = env_org_url os.environ["OKTA_CLIENT_TOKEN"] = env_token client = OktaClient() loaded_config = client.get_config() os.environ.pop("OKTA_CLIENT_ORGURL") os.environ.pop("OKTA_CLIENT_TOKEN") assert local_org_url != loaded_config['client']['orgUrl'] assert local_token != loaded_config['client']['token'] assert local_org_url != env_org_url assert local_token != env_token assert env_org_url == loaded_config['client']['orgUrl'] assert env_token == loaded_config['client']['token']
def test_client_ssl_context(monkeypatch, mocker): org_url = "https://test.okta.com" token = "TOKEN" mock_ssl_context = mocker.MagicMock() config = {'orgUrl': org_url, 'token': token, 'sslContext': mock_ssl_context} client = OktaClient(config) # mock http requests, verify if custom header is present in request class MockHTTPRequest(): def __call__(self, **params): self.request_info = params self.headers = params['headers'] self.url = params['url'] self.content_type = 'application/json' self.links = '' self.text = MockHTTPRequest.mock_response_text self.status = 200 return self async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return '[{"text": "mock response text"}]' mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp.ClientSession, 'request', mock_http_request) asyncio.run(client.list_users()) assert mock_http_request.request_info['ssl_context'] == mock_ssl_context
def test_client_raise_exception(): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token, 'raiseException': True} client = OktaClient(config) with pytest.raises(HTTPException): asyncio.run(client.list_users())
def test_constructor_user_config_SSWS(): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} client = OktaClient(user_config=config) loaded_config = client.get_config() assert org_url == loaded_config['client']['orgUrl'] assert token == loaded_config['client']['token']
def test_constructor_valid_no_proxy(): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} # Ensure no error is raised and proxy is None client = OktaClient(user_config=config) assert client.get_request_executor()._http_client._proxy is None
def test_constructor_user_config_Bearer(): authorizationMode = "Bearer" org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token, 'authorizationMode': authorizationMode} client = OktaClient(user_config=config) loaded_config = client.get_config() assert org_url == loaded_config['client']['orgUrl'] assert token == loaded_config['client']['token'] assert authorizationMode == loaded_config['client']['authorizationMode']
def test_client_log_debug(monkeypatch, caplog): org_url = "https://test.okta.com" token = "TOKEN" config = { 'orgUrl': org_url, 'token': token, 'logging': { 'enabled': True, 'logLevel': logging.DEBUG } } client = OktaClient(config) class MockHTTPRequest(): def __call__(self, **params): self.request_info = params self.headers = params['headers'] self.url = params['url'] self.content_type = 'application/json' self.links = '' self.text = MockHTTPRequest.mock_response_text self.status = 200 return self async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return '[{"embedded": null,' \ '"links": {"self": {"href": "https://test.okta.com/v1/users/test_id"}},' \ '"activated": "2021-01-01T00:00:00.000Z",' \ '"created": "2021-01-01T00:00:00.000Z",' \ '"credentials": null,' \ '"id": "test_id",' \ '"last_login": null,' \ '"profile": {"name": "test_name"},' \ '"status": null,' \ '"status_changed": null,' \ '"transitioning_to_status": null,' \ '"type": null}]' mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp, 'request', mock_http_request) with caplog.at_level(logging.DEBUG): res, resp_body, error = asyncio.run(client.list_users()) assert 'okta-sdk-python' in caplog.text assert 'DEBUG' in caplog.text assert "'method': 'GET'" in caplog.text assert "'url': 'https://test.okta.com/api/v1/users'" in caplog.text
def test_constructor_env_vars_SSWS(): org_url = "https://test.okta.com" token = "TOKEN" os.environ["OKTA_CLIENT_ORGURL"] = org_url os.environ["OKTA_CLIENT_TOKEN"] = token client = OktaClient() loaded_config = client.get_config() os.environ.pop("OKTA_CLIENT_ORGURL") os.environ.pop("OKTA_CLIENT_TOKEN") assert org_url == loaded_config['client']['orgUrl'] assert token == loaded_config['client']['token']
async def test_backoff_calculation(): client = Client(user_config=CLIENT_CONFIG) response_429 = (await mocks.mock_GET_HTTP_Client_response_429())[1] # ^ has a 1 second difference in retry and datetime # backoff should be 2 by Okta standards retry_limit_reset = float(response_429.headers["X-Rate-Limit-Reset"]) date_time = convert_date_time_to_seconds(response_429.headers["Date"]) backoff_time = client.get_request_executor().calculate_backoff( retry_limit_reset, date_time) assert (backoff_time == 2)
def test_constructor_local_config_SSWS(fs): fs.pause() local_sample = os.path.join(os.path.dirname( __file__), "files", "SSWS-sample-local.yaml") with open(local_sample) as file: local_config = yaml.load(file, Loader=yaml.SafeLoader) org_url = local_config["okta"]["client"]["orgUrl"] token = local_config["okta"]["client"]["token"] fs.resume() fs.create_file(_LOCAL_YAML_PATH, contents=yaml.dump(local_config)) client = OktaClient() loaded_config = client.get_config() assert org_url == loaded_config['client']['orgUrl'] assert token == loaded_config['client']['token']
async def test_client_error_call_oauth(fs, monkeypatch): oauth_client = Client({ "orgUrl": ORG_URL, "authorizationMode": "PrivateKey", "clientId": CLIENT_ID, "scopes": SCOPES, "privateKey": PRIVATE_KEY + "Wrong one" }) monkeypatch.setattr(OAuth, 'get_access_token', mocks.mock_access_token) req, err = await oauth_client.get_request_executor()\ .create_request("GET", GET_USERS_CALL, {}, {}) req, res_details, resp_body, error = await oauth_client\ .get_request_executor().fire_request(req) parsed, error = HTTPClient.check_response_for_error( req["url"], res_details, resp_body) assert parsed is None assert res_details.status == 404 assert isinstance(error, HTTPError) assert error.message.startswith("HTTP 404")
async def test_response_pagination_with_next(monkeypatch): ssws_client = Client({ "orgUrl": ORG_URL, "token": API_TOKEN }) req, error = await ssws_client.get_request_executor()\ .create_request("GET", GET_USERS_CALL + API_LIMIT, {}, {}) monkeypatch.setattr(RequestExecutor, 'fire_request', mocks.mock_GET_HTTP_Client_response_valid_with_next) result, error = await ssws_client.get_request_executor().execute(req) assert result.get_body() is not None assert result.has_next() monkeypatch.setattr(RequestExecutor, 'fire_request', mocks.mock_GET_HTTP_Client_response_valid) assert await result.next() is not None assert not result.has_next() with pytest.raises(StopAsyncIteration): await result.next()
def test_get_domain(self, monkeypatch): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} client = OktaClient(config) # mock http requests class MockHTTPRequest(): _mocked_response = None def __call__(self, **params): self.request_info = params self.headers = params['headers'] self.url = params['url'] self.content_type = 'application/json' self.links = '' self.text = MockHTTPRequest.mock_response_text self.status = 200 return self async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return GET_DOMAIN_RESP mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp.ClientSession, 'request', mock_http_request) domain_resp, _, err = asyncio.run( client.get_domain('OcDz6iRyjkaCTXkdo0g3')) assert err is None assert isinstance(domain_resp, Domain) assert isinstance(domain_resp.public_certificate, DomainCertificateMetadata) assert len(domain_resp.dns_records) > 0 for dns_record in domain_resp.dns_records: assert isinstance(dns_record, DnsRecord) assert domain_resp.domain == 'login.example.com'
def test_constructor_local_config_PK(fs): fs.pause() local_sample = os.path.join(os.path.dirname( __file__), "files", "PK-sample-local.yaml") with open(local_sample) as file: local_config = yaml.load(file, Loader=yaml.SafeLoader) org_url = local_config["okta"]["client"]["orgUrl"] client_id = local_config["okta"]["client"]["clientId"] private_key = local_config["okta"]["client"]["privateKey"] fs.resume() fs.create_file(_LOCAL_YAML_PATH, contents=yaml.dump(local_config)) client = OktaClient() loaded_config = client.get_config() assert org_url == loaded_config['client']['orgUrl'] assert client_id == loaded_config['client']['clientId'] assert private_key == loaded_config['client']['privateKey']
def test_unknown_sign_on_mode(): response = copy.deepcopy(SAML_APP_RESP_DICT) response["signOnMode"] = "UNKNOWN_SIGN_ON_MODE" expected = copy.deepcopy(EXPECTED_SAML_APP_AS_DICT) expected["signOnMode"] = ApplicationSignOnMode("UNKNOWN_SIGN_ON_MODE") expected["settings"] = { "app": {}, "notifications": { "vpn": { "network": { "connection": "DISABLED", "exclude": [], "include": [] } } }, } config = { "orgUrl": "https://test_org.okta.com", "token": "test_token", "requestExecutor": MockRequestExecutor, } client = Client(config) # check list applications client._request_executor.set_response([response]) event_loop = asyncio.get_event_loop() result, resp, err = event_loop.run_until_complete( client.list_applications()) # verify if result fallbacks to generic Application assert type(result[0]) != SamlApplication assert type(result[0]) == Application assert result[0].as_dict() == expected # check get application client._request_executor.set_response(response) event_loop = asyncio.get_event_loop() result, resp, err = event_loop.run_until_complete( client.get_application("test_id")) # verify if result fallbacks to generic Application assert type(result) != SamlApplication assert type(result) == Application assert result.as_dict() == expected
def test_constructor_valid_env_vars(): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} # Setting up env vars os.environ["HTTP_PROXY"] = "http://*****:*****@test.okta.com:8080" os.environ["HTTPS_PROXY"] = "https://*****:*****@test.okta.com:8080" expected = os.environ["HTTPS_PROXY"] client = OktaClient(user_config=config) # Deleting env vars del os.environ['HTTP_PROXY'] del os.environ['HTTPS_PROXY'] # Ensure no error is raised and proxy is None assert client.get_request_executor()._http_client._proxy == expected
def test_client_log_exception(monkeypatch, caplog): org_url = "https://test.okta.com" token = "TOKEN" config = { 'orgUrl': org_url, 'token': token, 'logging': { 'enabled': True, 'logLevel': logging.DEBUG } } client = OktaClient(config) class MockHTTPRequest(): def __call__(self, **params): raise aiohttp.ClientConnectorCertificateError( ConnectionKey(host=org_url, port=443, is_ssl=True, ssl=None, proxy=None, proxy_auth=None, proxy_headers_hash=None), SSLCertVerificationError( 1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' 'unable to get local issuer certificate (_ssl.c:1123)')) async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return '[{"text": "mock response text"}]' mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp, 'request', mock_http_request) with caplog.at_level(logging.DEBUG): res, resp_body, error = asyncio.run(client.list_users()) assert 'Cannot connect to host https://test.okta.com' in caplog.text
def test_constructor_user_config_PK(private_key): org_url = "https://test.okta.com" authorizationMode = "PrivateKey" client_id = "clientID" scopes = ["scope1"] config = { 'orgUrl': org_url, 'authorizationMode': authorizationMode, 'clientId': client_id, 'scopes': scopes, 'privateKey': private_key } client = OktaClient(user_config=config) loaded_config = client.get_config() assert org_url == loaded_config['client']['orgUrl'] assert authorizationMode == loaded_config['client']['authorizationMode'] assert client_id == loaded_config['client']['clientId'] assert scopes == loaded_config['client']['scopes'] assert private_key == loaded_config['client']['privateKey']
async def test_max_retries_no_timeout(monkeypatch, mocker): client = Client(user_config=CLIENT_CONFIG) query_params = {"limit": "1"} monkeypatch.setattr(HTTPClient, 'send_request', mocks.mock_GET_HTTP_Client_response_429) monkeypatch.setattr(time, 'sleep', mocks.mock_pause_function) http_spy = mocker.spy(HTTPClient, 'send_request') users, resp, error = await client.list_users(query_params) http_spy.assert_called() assert http_spy.call_count ==\ client.get_request_executor()._max_retries + 1 assert client.get_request_executor()._request_timeout == 0 assert isinstance(error, HTTPError) assert error is not None assert error.status == HTTPStatus.TOO_MANY_REQUESTS assert resp.get_status() == HTTPStatus.TOO_MANY_REQUESTS
def test_constructor_valid_proxy(): org_url = "https://test.okta.com" token = "TOKEN" port = 8080 host = "test.okta.com" username = "******" password = "******" config = { 'orgUrl': org_url, 'token': token, 'proxy': { 'port': port, 'host': host, 'username': username, 'password': password } } # Ensure no error is raised and correct proxy is determined client = OktaClient(user_config=config) assert client.get_request_executor( )._http_client._proxy == f"http://{username}:{password}@{host}:{port}/"
async def test_no_x_reset_header(monkeypatch): client = Client(user_config=CLIENT_CONFIG) monkeypatch.setattr(HTTPClient, 'send_request', mocks.mock_GET_HTTP_Client_response_429_no_x_reset) monkeypatch.setattr(time, 'sleep', mocks.mock_pause_function) users, resp, error = await client.list_users() assert error is not None assert isinstance(error, Exception) assert error.args[0] == ERROR_MESSAGE_429_MISSING_DATE_X_RESET assert users is None assert resp is None
def test_client_handle_aiohttp_error(monkeypatch, mocker): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} client = OktaClient(config) class MockHTTPRequest(): def __call__(self, **params): raise aiohttp.ClientConnectorCertificateError( ConnectionKey(host=org_url, port=443, is_ssl=True, ssl=None, proxy=None, proxy_auth=None, proxy_headers_hash=None), SSLCertVerificationError( 1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' 'unable to get local issuer certificate (_ssl.c:1123)')) async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return '[{"text": "mock response text"}]' mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp, 'request', mock_http_request) res, resp_body, error = asyncio.run(client.list_users()) assert res is None assert resp_body is None assert isinstance(error, aiohttp.ClientError)
def test_constructor_precedence_highest_rank_user_config(): # Setup env. vars env_org_url = "https://test.env.okta.com" env_token = "envTOKEN" os.environ["OKTA_CLIENT_ORGURL"] = env_org_url os.environ["OKTA_CLIENT_TOKEN"] = env_token # Setup user config user_org_url = "https://test.user.okta.com" user_token = "userTOKEN" config = {'orgUrl': user_org_url, 'token': user_token} client = OktaClient(config) loaded_config = client.get_config() os.environ.pop("OKTA_CLIENT_ORGURL") os.environ.pop("OKTA_CLIENT_TOKEN") assert user_org_url == loaded_config['client']['orgUrl'] assert user_token == loaded_config['client']['token'] assert user_org_url != env_org_url assert user_token != env_token assert env_org_url != loaded_config['client']['orgUrl'] assert env_token != loaded_config['client']['token']
def test_client_custom_headers(monkeypatch, mocker): org_url = "https://test.okta.com" token = "TOKEN" config = {'orgUrl': org_url, 'token': token} custom_headers = { 'Header-Test-1': 'test value 1', 'Header-Test-2': 'test value 2' } client = OktaClient(config) # verify custom headers are set client.set_custom_headers(custom_headers) assert client.get_custom_headers() == custom_headers # mock http requests, verify if custom header is present in request class MockHTTPRequest(): def __call__(self, **params): self.request_info = params self.headers = params['headers'] self.url = params['url'] self.content_type = 'application/json' self.links = '' self.text = MockHTTPRequest.mock_response_text self.status = 200 return self async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): pass @staticmethod async def mock_response_text(): return '{"text": "mock response text"}' mock_http_request = MockHTTPRequest() monkeypatch.setattr(aiohttp, 'request', mock_http_request) asyncio.run(client.list_users()) assert 'Header-Test-1' in mock_http_request.headers assert 'Header-Test-2' in mock_http_request.headers # verify custom headers are cleared client.clear_custom_headers() assert client.get_custom_headers() == {}
async def test_multiple_x_reset_headers(monkeypatch, mocker): client = Client(user_config=CLIENT_CONFIG) backoff_spy = mocker.spy(RequestExecutor, 'calculate_backoff') monkeypatch.setattr(HTTPClient, 'send_request', mocks.mock_GET_HTTP_Client_response_429_multi_x_reset) # the X Rate Limit Resets used are 1 sec and 2 sec after the Date header, # -> the min. one should be used (1 sec. after) and the backoff calculated # should be equal to 2 (by Okta standards) monkeypatch.setattr(time, 'sleep', mocks.mock_pause_function) users, resp, error = await client.list_users() assert error is not None assert users is None assert backoff_spy.spy_return == 2
async def test_response_headers(monkeypatch): ssws_client = Client({ "orgUrl": ORG_URL, "token": API_TOKEN }) req, error = await ssws_client.get_request_executor()\ .create_request("GET", GET_USERS_CALL + API_LIMIT, {}, {}) monkeypatch.setattr(RequestExecutor, 'fire_request', mocks.mock_GET_HTTP_Client_response_valid) result, error = await ssws_client.get_request_executor().execute(req) assert result.get_body() is not None assert result.get_headers() == mocks.MockHTTPResponseDetails().headers