From ae9d6a9485be7992c578dd6656b14a096f5afc5e Mon Sep 17 00:00:00 2001 From: Daniel Nogueira Date: Fri, 13 Feb 2026 11:50:06 -0500 Subject: [PATCH 1/2] updated oath capabilities --- README.md | 4 +++ http.json | 25 +++++++++++-- http_connector.py | 92 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 61d3383..78382dc 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **oauth_token_url** | optional | string | URL to fetch oauth token from | **client_id** | optional | string | Client ID (for OAuth) | **client_secret** | optional | password | Client Secret (for OAuth) | +**oauth_grant_type** | optional | string | OAuth grant_type value for token request body | +**oauth_scope** | optional | string | OAuth scope value for token request body | +**oauth_resource** | optional | string | OAuth resource value for token request body (legacy Azure AD) | +**oauth_extra_body** | optional | string | Additional OAuth token request body fields as a JSON object | **timeout** | optional | numeric | Timeout for HTTP calls | **test_http_method** | optional | string | HTTP Method for Test Connectivity | diff --git a/http.json b/http.json index 7438ea0..d8b0feb 100644 --- a/http.json +++ b/http.json @@ -78,14 +78,35 @@ "order": 9, "description": "Client Secret (for OAuth)" }, + "oauth_grant_type": { + "data_type": "string", + "order": 10, + "description": "OAuth grant_type value for token request body", + "default": "client_credentials" + }, + "oauth_scope": { + "data_type": "string", + "order": 11, + "description": "OAuth scope value for token request body" + }, + "oauth_resource": { + "data_type": "string", + "order": 12, + "description": "OAuth resource value for token request body (legacy Azure AD)" + }, + "oauth_extra_body": { + "data_type": "string", + "order": 13, + "description": "Additional OAuth token request body fields as a JSON object" + }, "timeout": { "data_type": "numeric", - "order": 10, + "order": 14, "description": "Timeout for HTTP calls" }, "test_http_method": { "data_type": "string", - "order": 11, + "order": 15, "description": "HTTP Method for Test Connectivity", "default": "GET", "value_list": [ diff --git a/http_connector.py b/http_connector.py index a8a4ed2..aa9bcbb 100644 --- a/http_connector.py +++ b/http_connector.py @@ -67,6 +67,10 @@ def __init__(self): self._oauth_token_url = None self._client_id = None self._client_secret = None + self._oauth_grant_type = None + self._oauth_scope = None + self._oauth_resource = None + self._oauth_extra_body = {} self._access_token = None self.access_token_retry = True @@ -160,6 +164,28 @@ def initialize(self): self._oauth_token_url = self._oauth_token_url.strip("/") self._client_id = config.get("client_id") self._client_secret = config.get("client_secret") + self._oauth_grant_type = config.get("oauth_grant_type", "client_credentials") + if isinstance(self._oauth_grant_type, str): + self._oauth_grant_type = self._oauth_grant_type.strip() + if not self._oauth_grant_type: + self._oauth_grant_type = "client_credentials" + + self._oauth_scope = config.get("oauth_scope") + if isinstance(self._oauth_scope, str): + self._oauth_scope = self._oauth_scope.strip() + if not self._oauth_scope: + self._oauth_scope = None + + self._oauth_resource = config.get("oauth_resource") + if isinstance(self._oauth_resource, str): + self._oauth_resource = self._oauth_resource.strip() + if not self._oauth_resource: + self._oauth_resource = None + + ret_val, self._oauth_extra_body = self._get_oauth_extra_body(config.get("oauth_extra_body")) + if phantom.is_fail(ret_val): + return self.get_status() + self._access_token = self._state.get(HTTP_JSON_ACCESS_TOKEN) if "test_path" in config: @@ -488,6 +514,67 @@ def _get_headers(self, action_result, headers): return RetVal(phantom.APP_SUCCESS, headers) + def _get_oauth_extra_body(self, oauth_extra_body): + if oauth_extra_body is None: + return RetVal(phantom.APP_SUCCESS, {}) + + if hasattr(oauth_extra_body, "decode"): + oauth_extra_body = oauth_extra_body.decode("utf-8") + + if not isinstance(oauth_extra_body, str): + return RetVal( + self.set_status(phantom.APP_ERROR, "'oauth_extra_body' must be a JSON object string"), + {}, + ) + + oauth_extra_body = oauth_extra_body.strip() + if not oauth_extra_body: + return RetVal(phantom.APP_SUCCESS, {}) + + try: + oauth_extra_body = json.loads(oauth_extra_body) + except Exception as e: + error_message = self._get_error_message_from_exception(e) + return RetVal( + self.set_status( + phantom.APP_ERROR, + f"Failed to parse 'oauth_extra_body' as JSON object. Details: {error_message}", + ), + {}, + ) + + if not isinstance(oauth_extra_body, dict): + return RetVal( + self.set_status(phantom.APP_ERROR, "'oauth_extra_body' must be a JSON object"), + {}, + ) + + return RetVal(phantom.APP_SUCCESS, oauth_extra_body) + + def _use_oauth_body_client_credentials(self): + has_custom_oauth_inputs = bool(self._oauth_scope or self._oauth_resource or self._oauth_extra_body) + return has_custom_oauth_inputs or self._oauth_grant_type != "client_credentials" + + def _build_oauth_payload(self): + payload = {} + + # Keep typed OAuth fields authoritative over generic extras. + payload.update(self._oauth_extra_body) + payload["grant_type"] = self._oauth_grant_type + + if self._oauth_scope: + payload["scope"] = self._oauth_scope + if self._oauth_resource: + payload["resource"] = self._oauth_resource + + if self._use_oauth_body_client_credentials(): + if self._client_id: + payload["client_id"] = self._client_id + if self._client_secret: + payload["client_secret"] = self._client_secret + + return payload + def _generate_api_token(self, action_result, new_token=False): """This function is used to generate token @@ -501,13 +588,14 @@ def _generate_api_token(self, action_result, new_token=False): self.save_progress("Using old token") return self._access_token - payload = {"grant_type": "client_credentials"} + payload = self._build_oauth_payload() + use_oauth_body_client_credentials = self._use_oauth_body_client_credentials() self.save_progress("Fetching new token") # Querying endpoint to generate token response = requests.post( self._oauth_token_url, - auth=HTTPBasicAuth(self._client_id, self._client_secret), # nosemgrep + auth=None if use_oauth_body_client_credentials else HTTPBasicAuth(self._client_id, self._client_secret), # nosemgrep data=payload, timeout=DEFAULT_REQUEST_TIMEOUT, ) From 2821b0322b308a4b989064fce7d7e6808809a68c Mon Sep 17 00:00:00 2001 From: Daniel Nogueira Date: Tue, 17 Feb 2026 10:58:13 -0500 Subject: [PATCH 2/2] update docs --- README.md | 17 ++++++++++++++++- http.json | 2 +- manual_readme_content.md | 15 +++++++++++++++ release_notes/unreleased.md | 2 ++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78382dc..39c1592 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,25 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION **oauth_grant_type** | optional | string | OAuth grant_type value for token request body | **oauth_scope** | optional | string | OAuth scope value for token request body | **oauth_resource** | optional | string | OAuth resource value for token request body (legacy Azure AD) | -**oauth_extra_body** | optional | string | Additional OAuth token request body fields as a JSON object | +**oauth_extra_body** | optional | string | Additional OAuth token request body fields as a JSON object string | **timeout** | optional | numeric | Timeout for HTTP calls | **test_http_method** | optional | string | HTTP Method for Test Connectivity | +### OAuth token request behavior + +OAuth mode is used when `oauth_token_url` and `client_id` are configured. + +By default, the connector requests tokens using HTTP Basic auth (`client_id` and `client_secret`) and `grant_type=client_credentials` in the request body. + +When any of these are provided, client credentials are sent in the request body instead of HTTP Basic auth: + +- `oauth_grant_type` set to a value other than `client_credentials` +- `oauth_scope` +- `oauth_resource` +- `oauth_extra_body` + +`oauth_extra_body` must be a JSON object string (for example, `{"audience":"https://api.example.com"}`). If duplicated keys are present, `oauth_grant_type`, `oauth_scope`, and `oauth_resource` take precedence over `oauth_extra_body`. + ### Supported Actions [test connectivity](#action-test-connectivity) - Validate connection using the configured credentials
diff --git a/http.json b/http.json index d8b0feb..608318a 100644 --- a/http.json +++ b/http.json @@ -97,7 +97,7 @@ "oauth_extra_body": { "data_type": "string", "order": 13, - "description": "Additional OAuth token request body fields as a JSON object" + "description": "Additional OAuth token request body fields as a JSON object string" }, "timeout": { "data_type": "numeric", diff --git a/manual_readme_content.md b/manual_readme_content.md index c70b8a2..fc9b97a 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -8,3 +8,18 @@ HTTPS) on your Phantom host(s) in order to function. 1. Basic Auth (username and password) 1. OAuth (oauth token url, client id and client secret) 1. Provided Auth token (auth_token_name, auth_token) + +### OAuth token request behavior + +OAuth mode is used when `oauth_token_url` and `client_id` are configured. + +By default, the connector requests tokens using HTTP Basic auth (`client_id` and `client_secret`) and `grant_type=client_credentials` in the request body. + +When any of these are provided, client credentials are sent in the request body instead of HTTP Basic auth: + +- `oauth_grant_type` set to a value other than `client_credentials` +- `oauth_scope` +- `oauth_resource` +- `oauth_extra_body` + +`oauth_extra_body` must be a JSON object string (for example, `{"audience":"https://api.example.com"}`). If duplicated keys are present, `oauth_grant_type`, `oauth_scope`, and `oauth_resource` take precedence over `oauth_extra_body`. diff --git a/release_notes/unreleased.md b/release_notes/unreleased.md index fbcb2fd..be4ccb1 100644 --- a/release_notes/unreleased.md +++ b/release_notes/unreleased.md @@ -1 +1,3 @@ **Unreleased** + +* Add optional OAuth token request fields (`oauth_grant_type`, `oauth_scope`, `oauth_resource`, `oauth_extra_body`) and support client credentials in request body when needed