Skip to content
Closed

Oath #61

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,28 @@ 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 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 <br>
Expand Down
25 changes: 23 additions & 2 deletions http.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 string"
},
"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": [
Expand Down
92 changes: 90 additions & 2 deletions http_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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,
)
Expand Down
15 changes: 15 additions & 0 deletions manual_readme_content.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
2 changes: 2 additions & 0 deletions release_notes/unreleased.md
Original file line number Diff line number Diff line change
@@ -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
Loading