GraphQL

The Generic GraphQL client can interact with any GraphQL server API.

Utilities · GraphQL

Details

IDGraphQL
ProviderOpen Source
CategoryUtilities
From Version5.0.0
Docker Imagedemisto/graphql:1.0.0.10120494
Supported ModulesAgentix XSIAM

README

Generic GraphQL client to interact with any GraphQL server API.

Configure GraphQL in Cortex

Parameter Description Required
url GraphQL Server URL (e.g. https://api.github.com/graphql) True
credentials Username / Header Name False
fetch_schema_from_transport Fetch the schema from the transport False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False

Authentication

The Username and Password integration parameters can be used to access server that require basic authentication.

These fields also support the use of API key headers. To use API key headers, specify the header name and value in the following format:
_header:<header_name> in the Username field, and the header value in the Password field.

For example, in order to use
GitHub GraphQL API, the parameters
should be set as follows:

  • Username : _header:Authorization
  • Password : bearer <PERSONAL-ACCESS-TOKEN>

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

graphql-query


Execute a query request to the GraphQL server.

Base Command

graphql-query

Input

Argument Name Description Required
query The GraphQL query to execute. Required
variables_names A comma-separated list of names, for example: flag,num,alpha Optional
variables_values A comma-separated list of values, for example: true,4,3.5 Optional
variables_types An optional comma-separated list of types, for example: boolean,number,number. Optional values are: string, boolean and number. If not provided, integers and booleans will be detected automatically, and the rest of the variables will be handled as strings. Optional
max_result_size Max result size in KBs. Default is 10. Optional
populate_context_data Whether to populate the result to the context data. Possible values are: true, false. Default is true. Optional
outputs_key_field Primary key field in the response to unique the object in the context data. Optional

Command Example

!graphql-query query="query($number_of_repos:Int!) {viewer {name repositories(last: $number_of_repos) { nodes { name } } } }" variables_names="number_of_repos" variables_values="3" variables_types="Int" max_result_size="10" populate_context_data="true"`

Context Example

{
    "GraphQL": {
        "viewer": {
            "repositories": {
                "nodes": [
                    {
                        "name": "content"
                    },
                    {
                        "name": "demisto-sdk"
                    },
                    {
                        "name": "content-docs"
                    }
                ]
            }
        }
    }
}

Human Readable Output

GraphQL Query Results

viewer
repositories: {“nodes”: [{“name”: “content”}, {“name”: “demisto-sdk”}, {“name”: “content-docs”}]}

graphql-mutation


Execute a mutation request to the GraphQL server.

Base Command

graphql-mutation

Input

Argument Name Description Required
query The GraphQL mutation to execute. Required
variables_names A comma-separated list of names, for example: flag,num,alpha Optional
variables_values A comma-separated list of values, for example: true,4,3.5 Optional
variables_types An optional comma-separated list of types, for example: boolean,number,number. Optional values are: string, boolean and number. If not provided, integers and booleans will be detected automatically, and the rest of the variables will be handled as strings. Optional
max_result_size Max result size in KBs. Default is 10. Optional
populate_context_data Whether to populate the result to the context data. Possible values are: true, false. Default is true. Optional
outputs_key_field Primary key field in the response to unique the object in the context data. Optional

Command Example

!graphql-mutation query="mutation AddReactionToIssue {addReaction(input:{subjectId:"MDU6SXNzdWUyMzEzOTE1NTE=",content:HOORAY}) {reaction {content} subject { id } } }" max_result_size="10" populate_context_data="true"`

Context Example

{
    "GraphQL": {
        "addReaction": {
            "reaction": {
                "content": "HORRAY"
            },
            "subject": {
                "id": "MDU6SXNzdWUyMzEzOTE1NTE="
            }
        }
    }
}

Human Readable Output

GraphQL Query Results

addReaction
reaction: {“content”: “HOORAY”}
subject: {“id”: “MDU6SXNzdWUyMzEzOTE1NTE=”}

Troubleshooting

  • If you are encountering the error GraphQLError: Cannot query field, you may be failing because of a schema validation error. Uncheck the Fetch the schema from the transport integration parameter to disable the schema validation.

Configuration parameters

  • url — GraphQL Server URL (e.g., https://api.github.com/graphql) (required)
  • credentials — Username / Header Name
  • fetch_schema_from_transport — Fetch the schema from the transport
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (2)

  • graphql-mutation

    Executes a mutation request to the GraphQL server.

  • graphql-query

    Executes a query request to the GraphQL server.

from collections.abc import Callable
from itertools import zip_longest

import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401
from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport

from CommonServerUserPython import *

# Disable insecure warnings
urllib3.disable_warnings()


CAST_MAPPING: dict[str, Callable] = {
    "string": str,
    "boolean": bool,
    "number": arg_to_number,
}


def execute_query(client: Client, args: dict) -> CommandResults:
    query = gql(args["query"])
    variables_names = argToList(args.get("variables_names", ""))
    variables_values = argToList(args.get("variables_values", ""))
    variables_types = argToList(args.get("variables_types", ""))
    if len(variables_names) != len(variables_values) or (variables_types and len(variables_types) != len(variables_values)):
        raise ValueError("The variable lists are not in the same length")
    variables = {}
    for variable_name, variable_value, variable_type in zip_longest(variables_names, variables_values, variables_types):
        if variable_type:
            variable_value = CAST_MAPPING[variable_type](variable_value)
        elif variable_value.isdigit():
            variable_value = int(variable_value)
        elif variable_value.lower() in {"true", "false"}:
            variable_value = bool(variable_value)
        variables[variable_name] = variable_value
    result = client.execute(query, variable_values=variables)
    if (result_size := sys.getsizeof(result)) > (max_result_size := float(args.get("max_result_size", 10))) * 10000:
        raise ValueError(f"Result size {result_size / 10000} KBs is larger then max result size {max_result_size} KBs")
    command_results_args = {
        "readable_output": tableToMarkdown("GraphQL Query Results", result),
        "raw_response": result,
        "outputs": result if argToBoolean(args.get("populate_context_data")) else None,
        "outputs_prefix": "GraphQL",
    }
    if args.get("outputs_key_field"):
        command_results_args["outputs_key_field"] = args.get("outputs_key_field")
    return CommandResults(**command_results_args)


def main() -> None:
    command = demisto.command()
    try:
        params = demisto.params()
        request_params = {
            "url": params.get("url"),
            "verify": not params.get("insecure", False),
            "retries": 3,
        }
        if credentials := params.get("credentials"):
            if (identifier := credentials.get("identifier", "")).startswith("_header:"):
                header_name = identifier.split("_header:")[1]
                header_value = credentials.get("password", "")
                request_params["headers"] = {header_name: header_value}
            else:
                request_params["auth"] = (identifier, credentials.get("password"))

        transport = RequestsHTTPTransport(**request_params)
        handle_proxy()
        fetch_schema_from_transport = params.get("fetch_schema_from_transport", True)
        if fetch_schema_from_transport is None:
            fetch_schema_from_transport = True
        client = Client(
            transport=transport,
            fetch_schema_from_transport=fetch_schema_from_transport,
        )

        demisto.debug(f"Command being called is {command}")
        if command == "test-module":
            with client as session:
                if not fetch_schema_from_transport:
                    # When schema fetching is disabled, the connection alone doesn't verify
                    # the GraphQL endpoint. Execute an introspection query to confirm connectivity.
                    session.execute(gql("{__typename}"))
            return_results("ok")
        elif command == "graphql-query":
            return_results(execute_query(client, demisto.args()))
        elif command == "graphql-mutation":
            return_results(execute_query(client, demisto.args()))
        else:
            raise NotImplementedError(f"Received an un-supported command: {command}")
    except Exception as e:
        return_error(f"Failed to execute {command} command. Error: {e!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()