From 0d655cfa6b6cebf32fd62bffc3cedb34eb51d403 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Fri, 15 Mar 2024 16:09:18 -0700 Subject: [PATCH 1/5] new jwt strat --- docs-graveyard/cli.md | 2 +- guardrails/classes/credentials.py | 3 +-- guardrails/cli/configure.py | 24 +++++++++-------------- guardrails/cli/server/auth.py | 2 +- guardrails/cli/server/hub_client.py | 7 +++---- tests/unit_tests/cli/test_configure.py | 27 +++++++++++++------------- 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/docs-graveyard/cli.md b/docs-graveyard/cli.md index 0e8615c36..37e3ee447 100644 --- a/docs-graveyard/cli.md +++ b/docs-graveyard/cli.md @@ -15,7 +15,7 @@ In addition to providing a command line interface for validation, the guardrails ### Configuration In order to access any Validators from the Hub that require authentication, you will need to set up your environment through the `guardrails configure` command. Before running `guardrails configure`, go to the [Validator Hub]() to generate your access tokens. [Add more detail on this process]. -Once you have your tokens, run `guardrails configure` and enter the client id and client secret you retrieved above. +Once you have your tokens, run `guardrails configure` and enter the auth token you retrieved above. Also, if you want to opt out of anonymous metrics collection, you can do this now by specifying `--no-metrics=true`. diff --git a/guardrails/classes/credentials.py b/guardrails/classes/credentials.py index f0fd11158..8874d372a 100644 --- a/guardrails/classes/credentials.py +++ b/guardrails/classes/credentials.py @@ -10,8 +10,7 @@ @dataclass class Credentials(Serializeable): id: Optional[str] = None - client_id: Optional[str] = None - client_secret: Optional[str] = None + token: Optional[str] = None no_metrics: Optional[bool] = False @staticmethod diff --git a/guardrails/cli/configure.py b/guardrails/cli/configure.py index 7606c6859..2c16a0bac 100644 --- a/guardrails/cli/configure.py +++ b/guardrails/cli/configure.py @@ -12,15 +12,14 @@ def save_configuration_file( - client_id: str, client_secret: str, no_metrics: bool + token: str, no_metrics: bool ) -> None: home = expanduser("~") guardrails_rc = os.path.join(home, ".guardrailsrc") with open(guardrails_rc, "w") as rc_file: lines = [ f"id={str(uuid.uuid4())}{os.linesep}", - f"client_id={client_id}{os.linesep}", - f"client_secret={client_secret}{os.linesep}", + f"token={token}{os.linesep}", f"no_metrics={str(no_metrics).lower()}", ] rc_file.writelines(lines) @@ -29,11 +28,8 @@ def save_configuration_file( @guardrails.command() def configure( - client_id: Optional[str] = typer.Option( - help="Your Guardrails Hub client ID.", default="" - ), - client_secret: Optional[str] = typer.Option( - help="Your Guardrails Hub client secret.", hide_input=True, default="" + token: Optional[str] = typer.Option( + help="Your Guardrails Hub auth token.", hide_input=True, default="" ), no_metrics: Optional[str] = typer.Option( help="Opt out of anonymous metrics collection.", default=False @@ -43,15 +39,13 @@ def configure( try: notice_message = """ - You can find your tokens at https://hub.guardrailsai.com/tokens + You can find your token at https://hub.guardrailsai.com/tokens """ logger.log(level=LEVELS.get("NOTICE"), msg=notice_message) # type: ignore - if not client_id: - client_id = typer.prompt("Client ID") - if not client_secret: - client_secret = typer.prompt("Client secret", hide_input=True) + if not token: + token = typer.prompt("Token", hide_input=True) logger.info("Configuring...") - save_configuration_file(client_id, client_secret, no_metrics) # type: ignore + save_configuration_file(token, no_metrics) # type: ignore logger.info("Validating credentials...") get_auth() @@ -72,7 +66,7 @@ def configure( logger.error(auth_error) logger.error( """ - Check that your Client ID and Client secret are correct and try again. + Check that your token is correct and try again. If you don't have your token credentials you can find them here: diff --git a/guardrails/cli/server/auth.py b/guardrails/cli/server/auth.py index 1d94ba9a6..78c80aaba 100644 --- a/guardrails/cli/server/auth.py +++ b/guardrails/cli/server/auth.py @@ -3,7 +3,7 @@ from guardrails.classes.credentials import Credentials - +# unused - for now def get_auth_token(creds: Credentials) -> str: if creds.client_id and creds.client_secret: audience = "https://validator-hub-service.guardrailsai.com" diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index 16409ebd3..5b1ff8500 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -6,7 +6,6 @@ from guardrails.classes.credentials import Credentials from guardrails.cli.logger import logger -from guardrails.cli.server.auth import get_auth_token from guardrails.cli.server.module_manifest import ModuleManifest validator_hub_service = "https://so4sg4q4pb.execute-api.us-east-1.amazonaws.com" @@ -64,7 +63,7 @@ def fetch_module_manifest( def fetch_module(module_name: str) -> ModuleManifest: creds = Credentials.from_rc_file(logger) - token = get_auth_token(creds) + token = creds.token module_manifest_json = fetch_module_manifest(module_name, token, creds.id) return ModuleManifest.from_dict(module_manifest_json) @@ -90,7 +89,7 @@ def get_validator_manifest(module_name: str): def get_auth(): try: creds = Credentials.from_rc_file(logger) - token = get_auth_token(creds) + token = creds.token auth_url = f"{validator_hub_service}/auth" response = fetch(auth_url, token, creds.id) if not response: @@ -106,7 +105,7 @@ def get_auth(): def post_validator_submit(package_name: str, content: str): try: creds = Credentials.from_rc_file(logger) - token = get_auth_token(creds) + token = creds.token submission_url = f"{validator_hub_service}/validator/submit" headers = { diff --git a/tests/unit_tests/cli/test_configure.py b/tests/unit_tests/cli/test_configure.py index 83ec4f798..33d1fe71a 100644 --- a/tests/unit_tests/cli/test_configure.py +++ b/tests/unit_tests/cli/test_configure.py @@ -6,15 +6,15 @@ @pytest.mark.parametrize( - "client_id,client_secret,no_metrics", + "token,no_metrics", [ # Note: typer defaults only work through the cli # ("mock_client_id", "mock_client_secret", None), - ("mock_client_id", "mock_client_secret", True), - ("mock_client_id", "mock_client_secret", False), + ("mock_token", True), + ("mock_token", False) ], ) -def test_configure(mocker, client_id, client_secret, no_metrics): +def test_configure(mocker, token, no_metrics): mock_save_configuration_file = mocker.patch( "guardrails.cli.configure.save_configuration_file" ) @@ -23,14 +23,14 @@ def test_configure(mocker, client_id, client_secret, no_metrics): from guardrails.cli.configure import configure - configure(client_id, client_secret, no_metrics) + configure(token, no_metrics) assert mock_logger_info.call_count == 2 expected_calls = [call("Configuring..."), call("Validating credentials...")] mock_logger_info.assert_has_calls(expected_calls) mock_save_configuration_file.assert_called_once_with( - client_id, client_secret, no_metrics + token, no_metrics ) assert mock_get_auth.call_count == 1 @@ -38,7 +38,7 @@ def test_configure(mocker, client_id, client_secret, no_metrics): def test_configure_prompting(mocker): mock_typer_prompt = mocker.patch("typer.prompt") - mock_typer_prompt.side_effect = ["id", "secret"] + mock_typer_prompt.side_effect = ["token"] mock_save_configuration_file = mocker.patch( "guardrails.cli.configure.save_configuration_file" ) @@ -47,17 +47,17 @@ def test_configure_prompting(mocker): from guardrails.cli.configure import configure - configure(None, None, False) + configure(None, False) - assert mock_typer_prompt.call_count == 2 - expected_calls = [call("Client ID"), call("Client secret", hide_input=True)] + assert mock_typer_prompt.call_count == 1 + expected_calls = [call("Token", hide_input=True)] mock_typer_prompt.assert_has_calls(expected_calls) assert mock_logger_info.call_count == 2 expected_calls = [call("Configuring..."), call("Validating credentials...")] mock_logger_info.assert_has_calls(expected_calls) - mock_save_configuration_file.assert_called_once_with("id", "secret", False) + mock_save_configuration_file.assert_called_once_with("token", False) assert mock_get_auth.call_count == 1 @@ -87,7 +87,7 @@ def test_save_configuration_file(mocker): from guardrails.cli.configure import save_configuration_file - save_configuration_file("id", "secret", True) + save_configuration_file("token", True) assert expanduser_mock.called is True join_spy.assert_called_once_with("/Home", ".guardrailsrc") @@ -96,8 +96,7 @@ def test_save_configuration_file(mocker): writelines_spy.assert_called_once_with( [ f"id=f49354e0-80c7-4591-81db-cc2f945e5f1e{os.linesep}", - f"client_id=id{os.linesep}", - f"client_secret=secret{os.linesep}", + f"token=token{os.linesep}", "no_metrics=true", ] ) From 018b57ad49500e45a5b63129ae1349fca7e3e348 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Fri, 15 Mar 2024 16:13:43 -0700 Subject: [PATCH 2/5] lint --- guardrails/cli/configure.py | 4 +-- guardrails/cli/server/auth.py | 43 +++++++++++++------------- guardrails/cli/server/hub_client.py | 2 +- tests/unit_tests/cli/test_configure.py | 6 ++-- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/guardrails/cli/configure.py b/guardrails/cli/configure.py index 2c16a0bac..d019410de 100644 --- a/guardrails/cli/configure.py +++ b/guardrails/cli/configure.py @@ -11,9 +11,7 @@ from guardrails.cli.server.hub_client import AuthenticationError, get_auth -def save_configuration_file( - token: str, no_metrics: bool -) -> None: +def save_configuration_file(token: str, no_metrics: bool) -> None: home = expanduser("~") guardrails_rc = os.path.join(home, ".guardrailsrc") with open(guardrails_rc, "w") as rc_file: diff --git a/guardrails/cli/server/auth.py b/guardrails/cli/server/auth.py index 78c80aaba..4394b2949 100644 --- a/guardrails/cli/server/auth.py +++ b/guardrails/cli/server/auth.py @@ -1,25 +1,26 @@ -import http.client -import json +# import http.client +# import json + +# from guardrails.classes.credentials import Credentials -from guardrails.classes.credentials import Credentials # unused - for now -def get_auth_token(creds: Credentials) -> str: - if creds.client_id and creds.client_secret: - audience = "https://validator-hub-service.guardrailsai.com" - conn = http.client.HTTPSConnection("guardrailsai.us.auth0.com") - payload = json.dumps( - { - "client_id": creds.client_id, - "client_secret": creds.client_secret, - "audience": audience, - "grant_type": "client_credentials", - } - ) - headers = {"content-type": "application/json"} - conn.request("POST", "/oauth/token", payload, headers) +# def get_auth_token(creds: Credentials) -> str: +# if creds.client_id and creds.client_secret: +# audience = "https://validator-hub-service.guardrailsai.com" +# conn = http.client.HTTPSConnection("guardrailsai.us.auth0.com") +# payload = json.dumps( +# { +# "client_id": creds.client_id, +# "client_secret": creds.client_secret, +# "audience": audience, +# "grant_type": "client_credentials", +# } +# ) +# headers = {"content-type": "application/json"} +# conn.request("POST", "/oauth/token", payload, headers) - res = conn.getresponse() - data = json.loads(res.read().decode("utf-8")) - return data.get("access_token", "") - return "" +# res = conn.getresponse() +# data = json.loads(res.read().decode("utf-8")) +# return data.get("access_token", "") +# return "" diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index 5b1ff8500..80f0ea8b1 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -51,7 +51,7 @@ def fetch(url: str, token: Optional[str], anonymousUserId: Optional[str]): def fetch_module_manifest( - module_name: str, token: str, anonymousUserId: Optional[str] = None + module_name: str, token: Optional[str], anonymousUserId: Optional[str] = None ) -> Dict[str, Any]: namespace, validator_name = module_name.split("/", 1) manifest_path = validator_manifest_endpoint.safe_substitute( diff --git a/tests/unit_tests/cli/test_configure.py b/tests/unit_tests/cli/test_configure.py index 33d1fe71a..af38e47d7 100644 --- a/tests/unit_tests/cli/test_configure.py +++ b/tests/unit_tests/cli/test_configure.py @@ -11,7 +11,7 @@ # Note: typer defaults only work through the cli # ("mock_client_id", "mock_client_secret", None), ("mock_token", True), - ("mock_token", False) + ("mock_token", False), ], ) def test_configure(mocker, token, no_metrics): @@ -29,9 +29,7 @@ def test_configure(mocker, token, no_metrics): expected_calls = [call("Configuring..."), call("Validating credentials...")] mock_logger_info.assert_has_calls(expected_calls) - mock_save_configuration_file.assert_called_once_with( - token, no_metrics - ) + mock_save_configuration_file.assert_called_once_with(token, no_metrics) assert mock_get_auth.call_count == 1 From ca7ae2559db105844f0286a4b7494d28ecc0e724 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Mon, 18 Mar 2024 14:08:44 -0700 Subject: [PATCH 3/5] check for token expiration --- guardrails/cli/server/hub_client.py | 31 +++++++++-- poetry.lock | 54 ++++++------------- pyproject.toml | 1 + .../unit_tests/cli/server/test_hub_client.py | 49 +++++++++++++++++ 4 files changed, 94 insertions(+), 41 deletions(-) diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index 80f0ea8b1..fd317e2f6 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -3,11 +3,20 @@ from typing import Any, Dict, Optional import requests +from jwt import JWT +from jwt.exceptions import JWTDecodeError from guardrails.classes.credentials import Credentials from guardrails.cli.logger import logger from guardrails.cli.server.module_manifest import ModuleManifest +TOKEN_EXPIRED_MESSAGE = ( + "Your token has expired. Please run `guardrails configure` to update your token." +) +TOKEN_INVALID_MESSAGE = ( + "Your token is invalid. Please run `guardrails configure` to update your token." +) + validator_hub_service = "https://so4sg4q4pb.execute-api.us-east-1.amazonaws.com" validator_manifest_endpoint = Template( "validator-manifests/${namespace}/${validator_name}" @@ -61,9 +70,25 @@ def fetch_module_manifest( return fetch(manifest_url, token, anonymousUserId) +def get_jwt_token(creds: Credentials) -> str | None: + token = creds.token + + # check for jwt expiration + if token: + try: + JWT().decode(token, do_verify=False) + except JWTDecodeError as e: + # if the error message includes "Expired", then the token is expired + if "Expired" in str(e): + raise Exception(TOKEN_EXPIRED_MESSAGE) + else: + raise Exception(TOKEN_INVALID_MESSAGE) + return token + + def fetch_module(module_name: str) -> ModuleManifest: creds = Credentials.from_rc_file(logger) - token = creds.token + token = get_jwt_token(creds) module_manifest_json = fetch_module_manifest(module_name, token, creds.id) return ModuleManifest.from_dict(module_manifest_json) @@ -89,7 +114,7 @@ def get_validator_manifest(module_name: str): def get_auth(): try: creds = Credentials.from_rc_file(logger) - token = creds.token + token = get_jwt_token(creds) auth_url = f"{validator_hub_service}/auth" response = fetch(auth_url, token, creds.id) if not response: @@ -105,7 +130,7 @@ def get_auth(): def post_validator_submit(package_name: str, content: str): try: creds = Credentials.from_rc_file(logger) - token = creds.token + token = get_jwt_token(creds) submission_url = f"{validator_hub_service}/validator/submit" headers = { diff --git a/poetry.lock b/poetry.lock index ff9d3f812..9f9827a6d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1647,62 +1647,29 @@ optional = true python-versions = ">=3.7" files = [ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] @@ -2607,6 +2574,19 @@ test-functional = ["jupytext[test]"] test-integration = ["ipykernel", "jupyter-server (!=2.11)", "jupytext[test-functional]", "nbconvert"] test-ui = ["calysto-bash"] +[[package]] +name = "jwt" +version = "1.3.1" +description = "JSON Web Token library for Python 3." +optional = false +python-versions = ">= 3.6" +files = [ + {file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"}, +] + +[package.dependencies] +cryptography = ">=3.1,<3.4.0 || >3.4.0" + [[package]] name = "keyring" version = "24.3.1" @@ -2743,7 +2723,6 @@ files = [ {file = "lxml-4.9.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e8f9f93a23634cfafbad6e46ad7d09e0f4a25a2400e4a64b1b7b7c0fbaa06d9d"}, {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3f3f00a9061605725df1816f5713d10cd94636347ed651abdbc75828df302b20"}, {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:953dd5481bd6252bd480d6ec431f61d7d87fdcbbb71b0d2bdcfc6ae00bb6fb10"}, - {file = "lxml-4.9.4-cp312-cp312-win32.whl", hash = "sha256:266f655d1baff9c47b52f529b5f6bec33f66042f65f7c56adde3fcf2ed62ae8b"}, {file = "lxml-4.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:f1faee2a831fe249e1bae9cbc68d3cd8a30f7e37851deee4d7962b17c410dd56"}, {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23d891e5bdc12e2e506e7d225d6aa929e0a0368c9916c1fddefab88166e98b20"}, {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e96a1788f24d03e8d61679f9881a883ecdf9c445a38f9ae3f3f193ab6c591c66"}, @@ -4166,8 +4145,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -5167,7 +5146,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -8130,4 +8108,4 @@ vectordb = ["faiss-cpu", "numpy"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "4396fde3c53a875ec912127122048d38243701998e454e32423e9911efc7a38c" +content-hash = "d049c28582f204644d47669c0d197eb954fe4a8cc0cf3bbcd5c0b35490cb6b1d" diff --git a/pyproject.toml b/pyproject.toml index 91281ee89..f81a7c99a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ langchain-core = "^0.1.18" coloredlogs = "^15.0.1" requests = "^2.31.0" guard-rails-api-client = {git = "https://github.com/guardrails-ai/guardrails-api-client.git", rev = "v0.0.2", subdirectory = "guard-rails-api-client"} +jwt = "^1.3.1" [tool.poetry.extras] diff --git a/tests/unit_tests/cli/server/test_hub_client.py b/tests/unit_tests/cli/server/test_hub_client.py index a7bafe600..eb21e3bea 100644 --- a/tests/unit_tests/cli/server/test_hub_client.py +++ b/tests/unit_tests/cli/server/test_hub_client.py @@ -1,4 +1,14 @@ +import datetime + import pytest +from jwt import JWT, jwk_from_dict + +from guardrails.classes.credentials import Credentials +from guardrails.cli.server.hub_client import ( + TOKEN_EXPIRED_MESSAGE, + TOKEN_INVALID_MESSAGE, + get_jwt_token, +) # TODO @@ -29,3 +39,42 @@ def test_get_validator_manifest(): @pytest.mark.skip() def test_get_auth(): assert 1 == 1 + + +def test_get_jwt_token(): + expiration = (datetime.datetime.now().timestamp() + 1000).__floor__() + + jwk = jwk_from_dict( + { + "alg": "HS256", + "kty": "oct", + "kid": "050bf691-4348-4891-940f-99af8354e82b", + "k": "eCE35cBrbRsO1GhrbxLXnGrVATgUFZDrPyyuOar4crw", + } + ) + + valid_jwt = JWT().encode( + { + "exp": expiration, + }, + jwk, + "HS256", + ) + creds = {"token": valid_jwt} + assert get_jwt_token(Credentials.from_dict(creds)) == valid_jwt + + with pytest.raises(Exception) as e: + expiration = (datetime.datetime.now().timestamp() - 1000).__floor__() + expired_jwt = JWT().encode( + { + "exp": expiration, + }, + jwk, + "HS256", + ) + get_jwt_token(Credentials.from_dict({"token": expired_jwt})) + assert str(e.value) == TOKEN_EXPIRED_MESSAGE + + with pytest.raises(Exception) as e: + get_jwt_token(Credentials.from_dict({"token": "invalid_token"})) + assert str(e.value) == TOKEN_INVALID_MESSAGE From 01c6226d823b3fac856175df75108f8926563d42 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Tue, 19 Mar 2024 11:34:45 -0700 Subject: [PATCH 4/5] 3.8 compatability for return type --- guardrails/cli/server/hub_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index fd317e2f6..f14bd6a3d 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -70,7 +70,7 @@ def fetch_module_manifest( return fetch(manifest_url, token, anonymousUserId) -def get_jwt_token(creds: Credentials) -> str | None: +def get_jwt_token(creds: Credentials) -> Optional[str]: token = creds.token # check for jwt expiration From b6fe617116ccbad1fa146f99eadfded1660b61a7 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Tue, 19 Mar 2024 11:46:40 -0700 Subject: [PATCH 5/5] fix tests --- tests/unit_tests/cli/server/test_hub_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/cli/server/test_hub_client.py b/tests/unit_tests/cli/server/test_hub_client.py index eb21e3bea..25062951b 100644 --- a/tests/unit_tests/cli/server/test_hub_client.py +++ b/tests/unit_tests/cli/server/test_hub_client.py @@ -1,4 +1,5 @@ import datetime +import math import pytest from jwt import JWT, jwk_from_dict @@ -42,7 +43,7 @@ def test_get_auth(): def test_get_jwt_token(): - expiration = (datetime.datetime.now().timestamp() + 1000).__floor__() + expiration = math.floor(datetime.datetime.now().timestamp() + 1000) jwk = jwk_from_dict( { @@ -64,7 +65,7 @@ def test_get_jwt_token(): assert get_jwt_token(Credentials.from_dict(creds)) == valid_jwt with pytest.raises(Exception) as e: - expiration = (datetime.datetime.now().timestamp() - 1000).__floor__() + expiration = math.floor(datetime.datetime.now().timestamp() - 1000) expired_jwt = JWT().encode( { "exp": expiration,