From ea6b5702a67315c4c520f8cf8795950ba59ecb15 Mon Sep 17 00:00:00 2001 From: Ray Han Date: Tue, 4 Jun 2024 09:17:06 -0700 Subject: [PATCH 1/5] add additional optional external auth options to support CSSO --- src/aerie_cli/aerie_host.py | 32 +++++++++++++++++++++++++++- src/aerie_cli/utils/sessions.py | 37 +++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/aerie_cli/aerie_host.py b/src/aerie_cli/aerie_host.py index 5fbebe5f..050df4ad 100644 --- a/src/aerie_cli/aerie_host.py +++ b/src/aerie_cli/aerie_host.py @@ -278,6 +278,18 @@ def authenticate(self, username: str, password: str = None): if not self.check_auth(): raise RuntimeError(f"Failed to open session") + def validateSSO(self): + resp = self.session.get(self.gateway_url + "/auth/validateSSO") + + if not resp.ok: + raise RuntimeError(f"failed to validate external credential") + resp_json = resp.json() + + self.aerie_jwt = AerieJWT(resp_json["token"]) + self.active_role = self.aerie_jwt.default_role + + if not self.check_auth(): + raise RuntimeError(f"Failed to open session") @define class ExternalAuthConfiguration: @@ -293,6 +305,9 @@ class ExternalAuthConfiguration: auth_url: str static_post_vars: dict secret_post_vars: list + data_as_headers: bool + token_cookie_mappings: list + secret_prompt_mappings: dict @classmethod def from_dict(cls, config: Dict) -> "ExternalAuthConfiguration": @@ -300,6 +315,18 @@ def from_dict(cls, config: Dict) -> "ExternalAuthConfiguration": auth_url = config["auth_url"] static_post_vars = config["static_post_vars"] secret_post_vars = config["secret_post_vars"] + try: + data_as_headers = config["data_as_headers"] + except KeyError: + data_as_header = False + try: + token_cookie_mappings = config["token_cookie_mappings"] + except KeyError: + token_cookie_mappings = [] + try: + secret_prompt_mappings = config["secret_prompt_mappings"] + except KeyError: + secret_prompt_mappings = {} except KeyError as e: raise ValueError( @@ -316,13 +343,16 @@ def from_dict(cls, config: Dict) -> "ExternalAuthConfiguration": "Invalid value for 'secret_post_vars' in external auth configuration" ) - return cls(auth_url, static_post_vars, secret_post_vars) + return cls(auth_url, static_post_vars, secret_post_vars, data_as_headers, token_cookie_mappings, secret_prompt_mappings) def to_dict(self) -> Dict: return { "auth_url": self.auth_url, "static_post_vars": self.static_post_vars, "secret_post_vars": self.secret_post_vars, + "data_as_headers": self.data_as_headers, + "token_cookie_mappings": self.token_cookie_mappings, + "secret_prompt_mappings": self.secret_prompt_mappings } diff --git a/src/aerie_cli/utils/sessions.py b/src/aerie_cli/utils/sessions.py index 6efd41f2..36e4cab5 100644 --- a/src/aerie_cli/utils/sessions.py +++ b/src/aerie_cli/utils/sessions.py @@ -99,20 +99,32 @@ def authenticate_with_external( secret_post_vars = {} for secret_var_name in configuration.secret_post_vars: + try: + secret_prompt = configuration.secret_prompt_mappings[secret_var_name] + except KeyError: + secret_prompt = secret_var_name + if secret_var_name in secret_post_vars.keys(): post_vars[secret_var_name] = secret_post_vars[secret_var_name] else: - post_vars[secret_var_name] = typer.prompt(f"External authentication - {secret_var_name}", hide_input=True) + post_vars[secret_var_name] = typer.prompt(f"External authentication - {secret_prompt}", hide_input=True) - resp = session.post(configuration.auth_url, json=post_vars) + if configuration.data_as_headers: + session.headers = post_vars + resp = session.post(configuration.auth_url) + else: + resp = session.post(configuration.auth_url, json=post_vars) if not resp.ok: raise RuntimeError( f"Failed to authenticate with proxy: {configuration.auth_url}" ) - return session + resp_json = resp.json() + for token in configuration.token_cookie_mappings: + session.cookies.set(resp_json[token['key']], resp_json[token['value']]) + return session def start_session_from_configuration( configuration: AerieHostConfiguration, @@ -153,15 +165,18 @@ def start_session_from_configuration( configuration.name, ) - if username is None: - if configuration.username is None: - username = typer.prompt("Aerie Username") - else: - username = configuration.username + if configuration.external_auth is None: + if username is None: + if configuration.username is None: + username = typer.prompt("Aerie Username") + else: + username = configuration.username - if password is None and hs.is_auth_enabled(): - password = typer.prompt("Aerie Password", hide_input=True) + if password is None and hs.is_auth_enabled(): + password = typer.prompt("Aerie Password", hide_input=True) - hs.authenticate(username, password) + hs.authenticate(username, password) + else: + hs.validateSSO() return hs From 64b37a281a0aef1aa50c4476e8dc9714358a9f31 Mon Sep 17 00:00:00 2001 From: Ray Han Date: Tue, 4 Jun 2024 09:39:23 -0700 Subject: [PATCH 2/5] add external auth options to README --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index cb08ff73..c9ead246 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,42 @@ In this example, the user would be prompted to enter a value for "password" and, "password": "my_password" } ``` +Additional `external_auth` settings for providers that require non standard communication pattern +|Key|Type|Description|Default| +|---|----|-----------|-------| +|data_as_headers|boolean|Send post_vars as request headers instead of body|False| +|token_cookie_mappings|List of Dict|Map tokens in response body to session cookies|[]| +|secret_prompt_mappings|Dict|Prompt for secret_post_vars with more descriptive names| +Example: + +```json + + { + "name": "my_host", + "graphql_url": "https://hostname/v1/graphql", + "gateway_url": "https://hostname/gateway", + "username": "my_username", + "external_auth": { + "auth_url": "https://auth_service/route", + "data_as_headers": true, + "static_post_vars": { + "username": "my_username" + }, + "secret_post_vars": [ + "x-password" + ], + "token_cookie_mappings": [ + { + "key": "token_name_key", + "value": "token_value_key" + } + ], + "secret_prompt_mappings": { + "x-password": "Password" + } + } + } +``` #### Using a Hasura Admin Secret From ea4265f76bba12f20889696659503937a16e74b5 Mon Sep 17 00:00:00 2001 From: Ray Han Date: Tue, 4 Jun 2024 09:42:49 -0700 Subject: [PATCH 3/5] add external auth options to README --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c9ead246..5fd4251e 100644 --- a/README.md +++ b/README.md @@ -219,10 +219,11 @@ In this example, the user would be prompted to enter a value for "password" and, ``` Additional `external_auth` settings for providers that require non standard communication pattern |Key|Type|Description|Default| -|---|----|-----------|-------| -|data_as_headers|boolean|Send post_vars as request headers instead of body|False| -|token_cookie_mappings|List of Dict|Map tokens in response body to session cookies|[]| -|secret_prompt_mappings|Dict|Prompt for secret_post_vars with more descriptive names| +|:--|:---|:----------|-------| +|`data_as_headers`|boolean|Send post_vars as request headers instead of body|False| +|`token_cookie_mappings`|List of Dict|Map tokens in response body to session cookies|[]| +|`secret_prompt_mappings`|Dict|Prompt for secret_post_vars with more descriptive names|{}| + Example: ```json From b74c83a980b5ea5b1422adc7d3ad4ef1c03886b2 Mon Sep 17 00:00:00 2001 From: Ray Han Date: Tue, 4 Jun 2024 09:44:49 -0700 Subject: [PATCH 4/5] add external auth options to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5fd4251e..45b31002 100644 --- a/README.md +++ b/README.md @@ -220,9 +220,9 @@ In this example, the user would be prompted to enter a value for "password" and, Additional `external_auth` settings for providers that require non standard communication pattern |Key|Type|Description|Default| |:--|:---|:----------|-------| -|`data_as_headers`|boolean|Send post_vars as request headers instead of body|False| -|`token_cookie_mappings`|List of Dict|Map tokens in response body to session cookies|[]| -|`secret_prompt_mappings`|Dict|Prompt for secret_post_vars with more descriptive names|{}| +|`data_as_headers`|`boolean`|Send post_vars as request headers instead of body|`False`| +|`token_cookie_mappings`|`list` of `dict`|Map tokens in response body to session cookies|`[]`| +|`secret_prompt_mappings`|`dict`|Prompt for `secret_post_vars` with more descriptive names|`{}`| Example: From 925a7ac6ddb1d6917c14a8c1b09deef2d25fd13b Mon Sep 17 00:00:00 2001 From: Ray Han Date: Mon, 17 Jun 2024 09:27:46 -0700 Subject: [PATCH 5/5] fix variable name --- src/aerie_cli/aerie_host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aerie_cli/aerie_host.py b/src/aerie_cli/aerie_host.py index 050df4ad..eb5a64b2 100644 --- a/src/aerie_cli/aerie_host.py +++ b/src/aerie_cli/aerie_host.py @@ -318,7 +318,7 @@ def from_dict(cls, config: Dict) -> "ExternalAuthConfiguration": try: data_as_headers = config["data_as_headers"] except KeyError: - data_as_header = False + data_as_headers = False try: token_cookie_mappings = config["token_cookie_mappings"] except KeyError: